@burdenoff/mcp-libs 2026.627.4 → 2026.627.5
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/modules/mybodyshield/index.cjs +1834 -0
- package/dist/modules/mybodyshield/index.cjs.map +1 -0
- package/dist/modules/mybodyshield/index.d.cts +8 -0
- package/dist/modules/mybodyshield/index.d.ts +8 -0
- package/dist/modules/mybodyshield/index.js +1773 -0
- package/dist/modules/mybodyshield/index.js.map +1 -0
- package/dist/modules/semanticfed/index.cjs +1846 -0
- package/dist/modules/semanticfed/index.cjs.map +1 -0
- package/dist/modules/semanticfed/index.d.cts +8 -0
- package/dist/modules/semanticfed/index.d.ts +8 -0
- package/dist/modules/semanticfed/index.js +1785 -0
- package/dist/modules/semanticfed/index.js.map +1 -0
- package/dist/modules/technospam/index.cjs +1962 -0
- package/dist/modules/technospam/index.cjs.map +1 -0
- package/dist/modules/technospam/index.d.cts +8 -0
- package/dist/modules/technospam/index.d.ts +8 -0
- package/dist/modules/technospam/index.js +1901 -0
- package/dist/modules/technospam/index.js.map +1 -0
- package/dist/modules/theculturepost/index.cjs +1587 -0
- package/dist/modules/theculturepost/index.cjs.map +1 -0
- package/dist/modules/theculturepost/index.d.cts +8 -0
- package/dist/modules/theculturepost/index.d.ts +8 -0
- package/dist/modules/theculturepost/index.js +1526 -0
- package/dist/modules/theculturepost/index.js.map +1 -0
- package/dist/modules/theglobalfuel/index.cjs +1678 -0
- package/dist/modules/theglobalfuel/index.cjs.map +1 -0
- package/dist/modules/theglobalfuel/index.d.cts +8 -0
- package/dist/modules/theglobalfuel/index.d.ts +8 -0
- package/dist/modules/theglobalfuel/index.js +1617 -0
- package/dist/modules/theglobalfuel/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1587 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zod = require('zod');
|
|
4
|
+
require('graphql-request');
|
|
5
|
+
require('cross-fetch');
|
|
6
|
+
require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
7
|
+
require('@modelcontextprotocol/sdk/types.js');
|
|
8
|
+
|
|
9
|
+
// src/modules/theculturepost/tools/stories.ts
|
|
10
|
+
|
|
11
|
+
// src/core/errors.ts
|
|
12
|
+
var McpInternalError = class extends Error {
|
|
13
|
+
cause;
|
|
14
|
+
constructor(message, cause) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "McpInternalError";
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/core/result.ts
|
|
22
|
+
function jsonResult(data, structured) {
|
|
23
|
+
const result = {
|
|
24
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
25
|
+
};
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function errorResult(message, hint) {
|
|
29
|
+
const text = hint ? `${message}
|
|
30
|
+
|
|
31
|
+
Hint: ${hint}` : message;
|
|
32
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/core/tool-name.ts
|
|
36
|
+
var MAX_TOOL_NAME_LENGTH = 63;
|
|
37
|
+
function assertSafeName(name, kind) {
|
|
38
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
39
|
+
throw new McpInternalError(
|
|
40
|
+
`Tool name '${name}' exceeds ${MAX_TOOL_NAME_LENGTH} characters (some MCP clients truncate at 64).`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
function mcpToolName(productName, ...parts) {
|
|
46
|
+
if (!productName) {
|
|
47
|
+
throw new McpInternalError("mcpToolName: productName is required");
|
|
48
|
+
}
|
|
49
|
+
const name = [productName, ...parts].filter(Boolean).join("_");
|
|
50
|
+
return assertSafeName(name);
|
|
51
|
+
}
|
|
52
|
+
function mcpResourceUri(productName, ...parts) {
|
|
53
|
+
if (!productName) {
|
|
54
|
+
throw new McpInternalError("mcpResourceUri: productName is required");
|
|
55
|
+
}
|
|
56
|
+
return `${productName}://${parts.filter(Boolean).join("/")}`;
|
|
57
|
+
}
|
|
58
|
+
function mcpPromptName(productName, ...parts) {
|
|
59
|
+
if (!productName) {
|
|
60
|
+
throw new McpInternalError("mcpPromptName: productName is required");
|
|
61
|
+
}
|
|
62
|
+
return [productName, ...parts].filter(Boolean).join("-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/modules/theculturepost/operations.ts
|
|
66
|
+
var STORY_FIELDS = `
|
|
67
|
+
id slug title summary body format status readingMins languages tags
|
|
68
|
+
topicId authorId editorId publishedAt workspaceId createdAt updatedAt
|
|
69
|
+
cover { id }
|
|
70
|
+
`;
|
|
71
|
+
var TOPIC_FIELDS = `
|
|
72
|
+
id slug name description category tradition isFeatured
|
|
73
|
+
workspaceId createdAt updatedAt
|
|
74
|
+
`;
|
|
75
|
+
var CITATION_FIELDS = `
|
|
76
|
+
id storyId title author publisher publishedOn url excerpt provenance
|
|
77
|
+
sourceType verification workspaceId createdAt updatedAt
|
|
78
|
+
sourceFile { id }
|
|
79
|
+
`;
|
|
80
|
+
var ARCHIVE_ITEM_FIELDS = `
|
|
81
|
+
id title description kind accessLevel collectionId languages tags origin
|
|
82
|
+
provenance datedAt workspaceId createdAt updatedAt
|
|
83
|
+
file { id }
|
|
84
|
+
`;
|
|
85
|
+
var COLLECTION_FIELDS = `
|
|
86
|
+
id slug name description tradition itemCount
|
|
87
|
+
workspaceId createdAt updatedAt
|
|
88
|
+
cover { id }
|
|
89
|
+
`;
|
|
90
|
+
var ORAL_HISTORY_FIELDS = `
|
|
91
|
+
id title narratorName language summary transcript durationSecs recordedAt
|
|
92
|
+
collectionId accessLevel workspaceId createdAt updatedAt
|
|
93
|
+
recording { id }
|
|
94
|
+
`;
|
|
95
|
+
var DISCUSSION_FIELDS = `
|
|
96
|
+
id title prompt storyId isInterfaith isLocked status replyCount tags
|
|
97
|
+
openedById workspaceId createdAt updatedAt
|
|
98
|
+
`;
|
|
99
|
+
var REPLY_FIELDS = `
|
|
100
|
+
id discussionId body perspective status authorId
|
|
101
|
+
workspaceId createdAt updatedAt
|
|
102
|
+
`;
|
|
103
|
+
var PAGE_INFO = `
|
|
104
|
+
pageInfo { cursor hasMore }
|
|
105
|
+
`;
|
|
106
|
+
var GET_STORY = `
|
|
107
|
+
query TheculturepostGetStory($id: ID!) {
|
|
108
|
+
tcpStory(id: $id) {
|
|
109
|
+
${STORY_FIELDS}
|
|
110
|
+
topic { id name slug category }
|
|
111
|
+
citations { id title sourceType verification url }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
var LIST_STORIES = `
|
|
116
|
+
query TheculturepostListStories(
|
|
117
|
+
$pagination: PaginationInput
|
|
118
|
+
$status: TcpStoryStatus
|
|
119
|
+
$topicId: ID
|
|
120
|
+
) {
|
|
121
|
+
tcpListStories(pagination: $pagination, status: $status, topicId: $topicId) {
|
|
122
|
+
items { ${STORY_FIELDS} }
|
|
123
|
+
${PAGE_INFO}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
var CREATE_STORY = `
|
|
128
|
+
mutation TheculturepostCreateStory($input: TcpCreateStoryInput!) {
|
|
129
|
+
tcpCreateStory(input: $input) { ${STORY_FIELDS} }
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
var UPDATE_STORY = `
|
|
133
|
+
mutation TheculturepostUpdateStory($id: ID!, $input: TcpUpdateStoryInput!) {
|
|
134
|
+
tcpUpdateStory(id: $id, input: $input) { ${STORY_FIELDS} }
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
var TRANSITION_STORY = `
|
|
138
|
+
mutation TheculturepostTransitionStory($id: ID!, $status: TcpStoryStatus!) {
|
|
139
|
+
tcpTransitionStory(id: $id, status: $status) { ${STORY_FIELDS} }
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
var GET_TOPIC = `
|
|
143
|
+
query TheculturepostGetTopic($id: ID!) {
|
|
144
|
+
tcpTopic(id: $id) {
|
|
145
|
+
${TOPIC_FIELDS}
|
|
146
|
+
stories { id title slug status }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
|
150
|
+
var LIST_TOPICS = `
|
|
151
|
+
query TheculturepostListTopics(
|
|
152
|
+
$category: TcpTopicCategory
|
|
153
|
+
$pagination: PaginationInput
|
|
154
|
+
) {
|
|
155
|
+
tcpListTopics(category: $category, pagination: $pagination) {
|
|
156
|
+
items { ${TOPIC_FIELDS} }
|
|
157
|
+
${PAGE_INFO}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
var CREATE_TOPIC = `
|
|
162
|
+
mutation TheculturepostCreateTopic($input: TcpCreateTopicInput!) {
|
|
163
|
+
tcpCreateTopic(input: $input) { ${TOPIC_FIELDS} }
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
var UPDATE_TOPIC = `
|
|
167
|
+
mutation TheculturepostUpdateTopic($id: ID!, $input: TcpUpdateTopicInput!) {
|
|
168
|
+
tcpUpdateTopic(id: $id, input: $input) { ${TOPIC_FIELDS} }
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
var ARCHIVE_TOPIC = `
|
|
172
|
+
mutation TheculturepostArchiveTopic($id: ID!) {
|
|
173
|
+
tcpArchiveTopic(id: $id)
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
var LIST_CITATIONS = `
|
|
177
|
+
query TheculturepostListCitations($storyId: ID!) {
|
|
178
|
+
tcpListCitations(storyId: $storyId) { ${CITATION_FIELDS} }
|
|
179
|
+
}
|
|
180
|
+
`;
|
|
181
|
+
var ADD_CITATION = `
|
|
182
|
+
mutation TheculturepostAddCitation($input: TcpCreateCitationInput!) {
|
|
183
|
+
tcpAddCitation(input: $input) { ${CITATION_FIELDS} }
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
var VERIFY_CITATION = `
|
|
187
|
+
mutation TheculturepostVerifyCitation(
|
|
188
|
+
$id: ID!
|
|
189
|
+
$verification: TcpVerificationStatus!
|
|
190
|
+
) {
|
|
191
|
+
tcpVerifyCitation(id: $id, verification: $verification) { ${CITATION_FIELDS} }
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
194
|
+
var GET_ARCHIVE_ITEM = `
|
|
195
|
+
query TheculturepostGetArchiveItem($id: ID!) {
|
|
196
|
+
tcpArchiveItem(id: $id) {
|
|
197
|
+
${ARCHIVE_ITEM_FIELDS}
|
|
198
|
+
collection { id name slug }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
var LIST_ARCHIVE_ITEMS = `
|
|
203
|
+
query TheculturepostListArchiveItems(
|
|
204
|
+
$accessLevel: TcpAccessLevel
|
|
205
|
+
$collectionId: ID
|
|
206
|
+
$kind: TcpArchiveItemKind
|
|
207
|
+
$pagination: PaginationInput
|
|
208
|
+
) {
|
|
209
|
+
tcpListArchiveItems(
|
|
210
|
+
accessLevel: $accessLevel
|
|
211
|
+
collectionId: $collectionId
|
|
212
|
+
kind: $kind
|
|
213
|
+
pagination: $pagination
|
|
214
|
+
) {
|
|
215
|
+
items { ${ARCHIVE_ITEM_FIELDS} }
|
|
216
|
+
${PAGE_INFO}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
`;
|
|
220
|
+
var CREATE_ARCHIVE_ITEM = `
|
|
221
|
+
mutation TheculturepostCreateArchiveItem($input: TcpCreateArchiveItemInput!) {
|
|
222
|
+
tcpCreateArchiveItem(input: $input) { ${ARCHIVE_ITEM_FIELDS} }
|
|
223
|
+
}
|
|
224
|
+
`;
|
|
225
|
+
var UPDATE_ARCHIVE_ITEM = `
|
|
226
|
+
mutation TheculturepostUpdateArchiveItem(
|
|
227
|
+
$id: ID!
|
|
228
|
+
$input: TcpUpdateArchiveItemInput!
|
|
229
|
+
) {
|
|
230
|
+
tcpUpdateArchiveItem(id: $id, input: $input) { ${ARCHIVE_ITEM_FIELDS} }
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
var ARCHIVE_ARCHIVE_ITEM = `
|
|
234
|
+
mutation TheculturepostArchiveArchiveItem($id: ID!) {
|
|
235
|
+
tcpArchiveArchiveItem(id: $id)
|
|
236
|
+
}
|
|
237
|
+
`;
|
|
238
|
+
var GET_COLLECTION = `
|
|
239
|
+
query TheculturepostGetCollection($id: ID!) {
|
|
240
|
+
tcpCollection(id: $id) {
|
|
241
|
+
${COLLECTION_FIELDS}
|
|
242
|
+
items { id title kind accessLevel }
|
|
243
|
+
oralHistories { id title narratorName }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
var LIST_COLLECTIONS = `
|
|
248
|
+
query TheculturepostListCollections($pagination: PaginationInput) {
|
|
249
|
+
tcpListCollections(pagination: $pagination) { ${COLLECTION_FIELDS} }
|
|
250
|
+
}
|
|
251
|
+
`;
|
|
252
|
+
var CREATE_COLLECTION = `
|
|
253
|
+
mutation TheculturepostCreateCollection($input: TcpCreateCollectionInput!) {
|
|
254
|
+
tcpCreateCollection(input: $input) { ${COLLECTION_FIELDS} }
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
var LIST_ORAL_HISTORIES = `
|
|
258
|
+
query TheculturepostListOralHistories($collectionId: ID) {
|
|
259
|
+
tcpListOralHistories(collectionId: $collectionId) { ${ORAL_HISTORY_FIELDS} }
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
var RECORD_ORAL_HISTORY = `
|
|
263
|
+
mutation TheculturepostRecordOralHistory($input: TcpCreateOralHistoryInput!) {
|
|
264
|
+
tcpRecordOralHistory(input: $input) { ${ORAL_HISTORY_FIELDS} }
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
var GET_DISCUSSION = `
|
|
268
|
+
query TheculturepostGetDiscussion($id: ID!) {
|
|
269
|
+
tcpDiscussion(id: $id) {
|
|
270
|
+
${DISCUSSION_FIELDS}
|
|
271
|
+
replies { id body perspective status authorId }
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
`;
|
|
275
|
+
var LIST_DISCUSSIONS = `
|
|
276
|
+
query TheculturepostListDiscussions(
|
|
277
|
+
$pagination: PaginationInput
|
|
278
|
+
$status: TcpModerationStatus
|
|
279
|
+
$storyId: ID
|
|
280
|
+
) {
|
|
281
|
+
tcpListDiscussions(pagination: $pagination, status: $status, storyId: $storyId) {
|
|
282
|
+
items { ${DISCUSSION_FIELDS} }
|
|
283
|
+
${PAGE_INFO}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
`;
|
|
287
|
+
var OPEN_DISCUSSION = `
|
|
288
|
+
mutation TheculturepostOpenDiscussion($input: TcpCreateDiscussionInput!) {
|
|
289
|
+
tcpOpenDiscussion(input: $input) { ${DISCUSSION_FIELDS} }
|
|
290
|
+
}
|
|
291
|
+
`;
|
|
292
|
+
var LOCK_DISCUSSION = `
|
|
293
|
+
mutation TheculturepostLockDiscussion($id: ID!) {
|
|
294
|
+
tcpLockDiscussion(id: $id) { ${DISCUSSION_FIELDS} }
|
|
295
|
+
}
|
|
296
|
+
`;
|
|
297
|
+
var LIST_REPLIES = `
|
|
298
|
+
query TheculturepostListReplies($discussionId: ID!, $status: TcpModerationStatus) {
|
|
299
|
+
tcpListReplies(discussionId: $discussionId, status: $status) { ${REPLY_FIELDS} }
|
|
300
|
+
}
|
|
301
|
+
`;
|
|
302
|
+
var POST_REPLY = `
|
|
303
|
+
mutation TheculturepostPostReply($input: TcpCreateReplyInput!) {
|
|
304
|
+
tcpPostReply(input: $input) { ${REPLY_FIELDS} }
|
|
305
|
+
}
|
|
306
|
+
`;
|
|
307
|
+
var MODERATE_REPLY = `
|
|
308
|
+
mutation TheculturepostModerateReply($id: ID!, $status: TcpModerationStatus!) {
|
|
309
|
+
tcpModerateReply(id: $id, status: $status) { ${REPLY_FIELDS} }
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
var HEALTH = `
|
|
313
|
+
query TheculturepostHealth {
|
|
314
|
+
tcpHealth
|
|
315
|
+
}
|
|
316
|
+
`;
|
|
317
|
+
var AccessLevelEnum = zod.z.enum(["COMMUNITY", "EMBARGOED", "PUBLIC", "RESTRICTED"]).describe("Visibility/access level of an archive item or oral history");
|
|
318
|
+
var ArchiveItemKindEnum = zod.z.enum(["AUDIO", "DOCUMENT", "MANUSCRIPT", "MAP", "OBJECT", "PHOTOGRAPH", "VIDEO"]).describe("Kind of cultural artifact in the archive");
|
|
319
|
+
var ModerationStatusEnum = zod.z.enum(["APPROVED", "FLAGGED", "PENDING", "REMOVED"]).describe("Moderation status of a discussion or reply");
|
|
320
|
+
var SourceTypeEnum = zod.z.enum([
|
|
321
|
+
"ACADEMIC",
|
|
322
|
+
"ARCHIVAL_RECORD",
|
|
323
|
+
"ARTIFACT",
|
|
324
|
+
"BOOK",
|
|
325
|
+
"INTERVIEW",
|
|
326
|
+
"PERIODICAL",
|
|
327
|
+
"PRIMARY_DOCUMENT",
|
|
328
|
+
"WEB"
|
|
329
|
+
]).describe("Type of cited source backing a story claim");
|
|
330
|
+
var StoryFormatEnum = zod.z.enum(["ARTICLE", "ESSAY", "EXPLAINER", "ORAL_HISTORY", "PHOTO_STORY", "TIMELINE"]).describe("Editorial format of a story");
|
|
331
|
+
var StoryStatusEnum = zod.z.enum(["ARCHIVED", "DRAFT", "IDEA", "IN_REVIEW", "PUBLISHED"]).describe("Editorial lifecycle status of a story");
|
|
332
|
+
var TopicCategoryEnum = zod.z.enum([
|
|
333
|
+
"CULTURE_AND_IDENTITY",
|
|
334
|
+
"FESTIVALS_AND_RITUALS",
|
|
335
|
+
"HERITAGE_AND_ARCHIVES",
|
|
336
|
+
"HISTORY",
|
|
337
|
+
"MIGRATION_AND_DIASPORA",
|
|
338
|
+
"ORAL_HISTORIES",
|
|
339
|
+
"PHILOSOPHY_AND_THOUGHT",
|
|
340
|
+
"RELIGION_AND_FAITH",
|
|
341
|
+
"VALUES_AND_ETHICS"
|
|
342
|
+
]).describe("Thematic category of a topic");
|
|
343
|
+
var VerificationStatusEnum = zod.z.enum(["CORROBORATED", "DISPUTED", "RETRACTED", "UNVERIFIED"]).describe("Verification status of a citation");
|
|
344
|
+
var paginationShape = {
|
|
345
|
+
cursor: zod.z.string().optional().describe("Opaque cursor for keyset pagination"),
|
|
346
|
+
limit: zod.z.number().int().optional().describe("Page size (default 50)"),
|
|
347
|
+
offset: zod.z.number().int().optional().describe("Pagination offset (default 0)")
|
|
348
|
+
};
|
|
349
|
+
function buildPagination(args) {
|
|
350
|
+
const pagination = {};
|
|
351
|
+
if (args.cursor !== void 0) pagination.cursor = args.cursor;
|
|
352
|
+
if (args.limit !== void 0) pagination.limit = args.limit;
|
|
353
|
+
if (args.offset !== void 0) pagination.offset = args.offset;
|
|
354
|
+
return Object.keys(pagination).length > 0 ? pagination : void 0;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/modules/theculturepost/tools/stories.ts
|
|
358
|
+
function registerStoryTools(server, ctx, track) {
|
|
359
|
+
server.registerTool(
|
|
360
|
+
track(mcpToolName(ctx.productName, "stories_get")),
|
|
361
|
+
{
|
|
362
|
+
title: "Get Story",
|
|
363
|
+
description: "Fetch a single TheCulturePost story by ID, including its topic and citations.",
|
|
364
|
+
inputSchema: { id: zod.z.string().describe("Story ID") }
|
|
365
|
+
},
|
|
366
|
+
async ({ id }) => {
|
|
367
|
+
try {
|
|
368
|
+
const data = await ctx.gql.workspace.request(GET_STORY, { id });
|
|
369
|
+
if (!data.tcpStory) return errorResult(`Story ${id} not found`);
|
|
370
|
+
return jsonResult(data.tcpStory);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
ctx.logger.error("theculturepost stories_get failed", { error: String(error) });
|
|
373
|
+
return errorResult(
|
|
374
|
+
`Failed to get story: ${error instanceof Error ? error.message : String(error)}`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
server.registerTool(
|
|
380
|
+
track(mcpToolName(ctx.productName, "stories_list")),
|
|
381
|
+
{
|
|
382
|
+
title: "List Stories",
|
|
383
|
+
description: "List stories in the bound workspace, optionally filtered by editorial status and/or topic. Returns a connection ({ items, pageInfo }).",
|
|
384
|
+
inputSchema: {
|
|
385
|
+
status: StoryStatusEnum.optional().describe("Filter by editorial status"),
|
|
386
|
+
topicId: zod.z.string().optional().describe("Filter by topic ID"),
|
|
387
|
+
...paginationShape
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
async ({ status, topicId, cursor, limit, offset }) => {
|
|
391
|
+
try {
|
|
392
|
+
const data = await ctx.gql.workspace.request(LIST_STORIES, {
|
|
393
|
+
status,
|
|
394
|
+
topicId,
|
|
395
|
+
pagination: buildPagination({ cursor, limit, offset })
|
|
396
|
+
});
|
|
397
|
+
return jsonResult(data.tcpListStories);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
ctx.logger.error("theculturepost stories_list failed", { error: String(error) });
|
|
400
|
+
return errorResult(
|
|
401
|
+
`Failed to list stories: ${error instanceof Error ? error.message : String(error)}`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
server.registerTool(
|
|
407
|
+
track(mcpToolName(ctx.productName, "stories_create")),
|
|
408
|
+
{
|
|
409
|
+
title: "Create Story",
|
|
410
|
+
description: "Draft a new story. `slug` must be unique within the workspace. Stories start in DRAFT (or IDEA) and advance via stories_transition.",
|
|
411
|
+
inputSchema: {
|
|
412
|
+
title: zod.z.string().min(1).describe("Story headline"),
|
|
413
|
+
slug: zod.z.string().min(1).describe("URL slug, unique within the workspace"),
|
|
414
|
+
topicId: zod.z.string().optional().describe("Owning topic ID"),
|
|
415
|
+
summary: zod.z.string().optional().describe("Short standfirst / dek"),
|
|
416
|
+
body: zod.z.string().optional().describe("Story body (markdown/rich text)"),
|
|
417
|
+
format: StoryFormatEnum.optional().describe("Editorial format (default ARTICLE)"),
|
|
418
|
+
languages: zod.z.array(zod.z.string()).optional().describe("ISO language codes the story is in"),
|
|
419
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Free-form tags"),
|
|
420
|
+
readingMins: zod.z.number().int().optional().describe("Estimated reading time in minutes"),
|
|
421
|
+
authorId: zod.z.string().optional().describe("Author user ID"),
|
|
422
|
+
editorId: zod.z.string().optional().describe("Editor user ID"),
|
|
423
|
+
coverFileId: zod.z.string().optional().describe("wspace-files-svc File ID for the hero image")
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
async ({
|
|
427
|
+
title,
|
|
428
|
+
slug,
|
|
429
|
+
topicId,
|
|
430
|
+
summary,
|
|
431
|
+
body,
|
|
432
|
+
format,
|
|
433
|
+
languages,
|
|
434
|
+
tags,
|
|
435
|
+
readingMins,
|
|
436
|
+
authorId,
|
|
437
|
+
editorId,
|
|
438
|
+
coverFileId
|
|
439
|
+
}) => {
|
|
440
|
+
try {
|
|
441
|
+
const input = { title, slug };
|
|
442
|
+
if (topicId !== void 0) input.topicId = topicId;
|
|
443
|
+
if (summary !== void 0) input.summary = summary;
|
|
444
|
+
if (body !== void 0) input.body = body;
|
|
445
|
+
if (format !== void 0) input.format = format;
|
|
446
|
+
if (languages !== void 0) input.languages = languages;
|
|
447
|
+
if (tags !== void 0) input.tags = tags;
|
|
448
|
+
if (readingMins !== void 0) input.readingMins = readingMins;
|
|
449
|
+
if (authorId !== void 0) input.authorId = authorId;
|
|
450
|
+
if (editorId !== void 0) input.editorId = editorId;
|
|
451
|
+
if (coverFileId !== void 0) input.coverFileId = coverFileId;
|
|
452
|
+
const data = await ctx.gql.workspace.request(CREATE_STORY, { input });
|
|
453
|
+
return jsonResult(data.tcpCreateStory);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
ctx.logger.error("theculturepost stories_create failed", { error: String(error) });
|
|
456
|
+
return errorResult(
|
|
457
|
+
`Failed to create story: ${error instanceof Error ? error.message : String(error)}`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
);
|
|
462
|
+
server.registerTool(
|
|
463
|
+
track(mcpToolName(ctx.productName, "stories_update")),
|
|
464
|
+
{
|
|
465
|
+
title: "Update Story",
|
|
466
|
+
description: "Update a story\u2019s editorial fields. At least one mutable field must be provided. Use stories_transition to change `status`.",
|
|
467
|
+
inputSchema: {
|
|
468
|
+
id: zod.z.string().describe("Story ID"),
|
|
469
|
+
title: zod.z.string().optional().describe("Updated headline"),
|
|
470
|
+
summary: zod.z.string().optional().describe("Updated standfirst / dek"),
|
|
471
|
+
body: zod.z.string().optional().describe("Updated body"),
|
|
472
|
+
format: StoryFormatEnum.optional().describe("Updated editorial format"),
|
|
473
|
+
languages: zod.z.array(zod.z.string()).optional().describe("Updated language codes"),
|
|
474
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tags"),
|
|
475
|
+
readingMins: zod.z.number().int().optional().describe("Updated reading time"),
|
|
476
|
+
topicId: zod.z.string().optional().describe("Reassign to a topic"),
|
|
477
|
+
authorId: zod.z.string().optional().describe("Updated author user ID"),
|
|
478
|
+
editorId: zod.z.string().optional().describe("Updated editor user ID"),
|
|
479
|
+
coverFileId: zod.z.string().optional().describe("Updated hero-image File ID")
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
async ({
|
|
483
|
+
id,
|
|
484
|
+
title,
|
|
485
|
+
summary,
|
|
486
|
+
body,
|
|
487
|
+
format,
|
|
488
|
+
languages,
|
|
489
|
+
tags,
|
|
490
|
+
readingMins,
|
|
491
|
+
topicId,
|
|
492
|
+
authorId,
|
|
493
|
+
editorId,
|
|
494
|
+
coverFileId
|
|
495
|
+
}) => {
|
|
496
|
+
try {
|
|
497
|
+
const input = {};
|
|
498
|
+
if (title !== void 0) input.title = title;
|
|
499
|
+
if (summary !== void 0) input.summary = summary;
|
|
500
|
+
if (body !== void 0) input.body = body;
|
|
501
|
+
if (format !== void 0) input.format = format;
|
|
502
|
+
if (languages !== void 0) input.languages = languages;
|
|
503
|
+
if (tags !== void 0) input.tags = tags;
|
|
504
|
+
if (readingMins !== void 0) input.readingMins = readingMins;
|
|
505
|
+
if (topicId !== void 0) input.topicId = topicId;
|
|
506
|
+
if (authorId !== void 0) input.authorId = authorId;
|
|
507
|
+
if (editorId !== void 0) input.editorId = editorId;
|
|
508
|
+
if (coverFileId !== void 0) input.coverFileId = coverFileId;
|
|
509
|
+
if (Object.keys(input).length === 0) {
|
|
510
|
+
return errorResult(
|
|
511
|
+
"No updates specified \u2014 provide at least one of title/summary/body/format/languages/tags/readingMins/topicId/authorId/editorId/coverFileId"
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const data = await ctx.gql.workspace.request(UPDATE_STORY, { id, input });
|
|
515
|
+
return jsonResult(data.tcpUpdateStory);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
ctx.logger.error("theculturepost stories_update failed", { error: String(error) });
|
|
518
|
+
return errorResult(
|
|
519
|
+
`Failed to update story: ${error instanceof Error ? error.message : String(error)}`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
);
|
|
524
|
+
server.registerTool(
|
|
525
|
+
track(mcpToolName(ctx.productName, "stories_transition")),
|
|
526
|
+
{
|
|
527
|
+
title: "Transition Story",
|
|
528
|
+
description: "Advance a story through its editorial lifecycle (validated transitions): IDEA \u2192 DRAFT \u2192 IN_REVIEW \u2192 PUBLISHED \u2192 ARCHIVED.",
|
|
529
|
+
inputSchema: {
|
|
530
|
+
id: zod.z.string().describe("Story ID"),
|
|
531
|
+
status: StoryStatusEnum.describe("Target editorial status")
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
async ({ id, status }) => {
|
|
535
|
+
try {
|
|
536
|
+
const data = await ctx.gql.workspace.request(TRANSITION_STORY, { id, status });
|
|
537
|
+
return jsonResult(data.tcpTransitionStory);
|
|
538
|
+
} catch (error) {
|
|
539
|
+
ctx.logger.error("theculturepost stories_transition failed", { error: String(error) });
|
|
540
|
+
return errorResult(
|
|
541
|
+
`Failed to transition story: ${error instanceof Error ? error.message : String(error)}`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
function registerTopicTools(server, ctx, track) {
|
|
548
|
+
server.registerTool(
|
|
549
|
+
track(mcpToolName(ctx.productName, "topics_get")),
|
|
550
|
+
{
|
|
551
|
+
title: "Get Topic",
|
|
552
|
+
description: "Fetch a single topic by ID, including its stories.",
|
|
553
|
+
inputSchema: { id: zod.z.string().describe("Topic ID") }
|
|
554
|
+
},
|
|
555
|
+
async ({ id }) => {
|
|
556
|
+
try {
|
|
557
|
+
const data = await ctx.gql.workspace.request(GET_TOPIC, { id });
|
|
558
|
+
if (!data.tcpTopic) return errorResult(`Topic ${id} not found`);
|
|
559
|
+
return jsonResult(data.tcpTopic);
|
|
560
|
+
} catch (error) {
|
|
561
|
+
ctx.logger.error("theculturepost topics_get failed", { error: String(error) });
|
|
562
|
+
return errorResult(
|
|
563
|
+
`Failed to get topic: ${error instanceof Error ? error.message : String(error)}`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
);
|
|
568
|
+
server.registerTool(
|
|
569
|
+
track(mcpToolName(ctx.productName, "topics_list")),
|
|
570
|
+
{
|
|
571
|
+
title: "List Topics",
|
|
572
|
+
description: "List topics in the bound workspace, optionally filtered by category. Returns a connection ({ items, pageInfo }).",
|
|
573
|
+
inputSchema: {
|
|
574
|
+
category: TopicCategoryEnum.optional().describe("Filter by thematic category"),
|
|
575
|
+
...paginationShape
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
async ({ category, cursor, limit, offset }) => {
|
|
579
|
+
try {
|
|
580
|
+
const data = await ctx.gql.workspace.request(LIST_TOPICS, {
|
|
581
|
+
category,
|
|
582
|
+
pagination: buildPagination({ cursor, limit, offset })
|
|
583
|
+
});
|
|
584
|
+
return jsonResult(data.tcpListTopics);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
ctx.logger.error("theculturepost topics_list failed", { error: String(error) });
|
|
587
|
+
return errorResult(
|
|
588
|
+
`Failed to list topics: ${error instanceof Error ? error.message : String(error)}`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
server.registerTool(
|
|
594
|
+
track(mcpToolName(ctx.productName, "topics_create")),
|
|
595
|
+
{
|
|
596
|
+
title: "Create Topic",
|
|
597
|
+
description: "Create a new topic. `slug` must be unique within the workspace. Default category is CULTURE_AND_IDENTITY.",
|
|
598
|
+
inputSchema: {
|
|
599
|
+
name: zod.z.string().min(1).describe("Topic display name"),
|
|
600
|
+
slug: zod.z.string().min(1).describe("URL slug, unique within the workspace"),
|
|
601
|
+
category: TopicCategoryEnum.optional().describe(
|
|
602
|
+
"Thematic category (default CULTURE_AND_IDENTITY)"
|
|
603
|
+
),
|
|
604
|
+
description: zod.z.string().optional().describe("Topic description"),
|
|
605
|
+
tradition: zod.z.string().optional().describe("Associated cultural/religious tradition"),
|
|
606
|
+
isFeatured: zod.z.boolean().optional().describe("Pin to the featured rail (default false)")
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
async ({ name, slug, category, description, tradition, isFeatured }) => {
|
|
610
|
+
try {
|
|
611
|
+
const input = { name, slug };
|
|
612
|
+
if (category !== void 0) input.category = category;
|
|
613
|
+
if (description !== void 0) input.description = description;
|
|
614
|
+
if (tradition !== void 0) input.tradition = tradition;
|
|
615
|
+
if (isFeatured !== void 0) input.isFeatured = isFeatured;
|
|
616
|
+
const data = await ctx.gql.workspace.request(CREATE_TOPIC, { input });
|
|
617
|
+
return jsonResult(data.tcpCreateTopic);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
ctx.logger.error("theculturepost topics_create failed", { error: String(error) });
|
|
620
|
+
return errorResult(
|
|
621
|
+
`Failed to create topic: ${error instanceof Error ? error.message : String(error)}`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
server.registerTool(
|
|
627
|
+
track(mcpToolName(ctx.productName, "topics_update")),
|
|
628
|
+
{
|
|
629
|
+
title: "Update Topic",
|
|
630
|
+
description: "Update a topic. At least one mutable field must be provided. (Slug is immutable.)",
|
|
631
|
+
inputSchema: {
|
|
632
|
+
id: zod.z.string().describe("Topic ID"),
|
|
633
|
+
name: zod.z.string().optional().describe("Updated display name"),
|
|
634
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
635
|
+
category: TopicCategoryEnum.optional().describe("Updated thematic category"),
|
|
636
|
+
tradition: zod.z.string().optional().describe("Updated associated tradition"),
|
|
637
|
+
isFeatured: zod.z.boolean().optional().describe("Updated featured flag")
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
async ({ id, name, description, category, tradition, isFeatured }) => {
|
|
641
|
+
try {
|
|
642
|
+
const input = {};
|
|
643
|
+
if (name !== void 0) input.name = name;
|
|
644
|
+
if (description !== void 0) input.description = description;
|
|
645
|
+
if (category !== void 0) input.category = category;
|
|
646
|
+
if (tradition !== void 0) input.tradition = tradition;
|
|
647
|
+
if (isFeatured !== void 0) input.isFeatured = isFeatured;
|
|
648
|
+
if (Object.keys(input).length === 0) {
|
|
649
|
+
return errorResult(
|
|
650
|
+
"No updates specified \u2014 provide at least one of name/description/category/tradition/isFeatured"
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const data = await ctx.gql.workspace.request(UPDATE_TOPIC, { id, input });
|
|
654
|
+
return jsonResult(data.tcpUpdateTopic);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
ctx.logger.error("theculturepost topics_update failed", { error: String(error) });
|
|
657
|
+
return errorResult(
|
|
658
|
+
`Failed to update topic: ${error instanceof Error ? error.message : String(error)}`
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
server.registerTool(
|
|
664
|
+
track(mcpToolName(ctx.productName, "topics_archive")),
|
|
665
|
+
{
|
|
666
|
+
title: "Archive Topic",
|
|
667
|
+
description: "Archive (soft-delete) a topic. Returns true on success.",
|
|
668
|
+
inputSchema: { id: zod.z.string().describe("Topic ID") }
|
|
669
|
+
},
|
|
670
|
+
async ({ id }) => {
|
|
671
|
+
try {
|
|
672
|
+
const data = await ctx.gql.workspace.request(
|
|
673
|
+
ARCHIVE_TOPIC,
|
|
674
|
+
{ id }
|
|
675
|
+
);
|
|
676
|
+
return jsonResult({ archived: data.tcpArchiveTopic, id });
|
|
677
|
+
} catch (error) {
|
|
678
|
+
ctx.logger.error("theculturepost topics_archive failed", { error: String(error) });
|
|
679
|
+
return errorResult(
|
|
680
|
+
`Failed to archive topic: ${error instanceof Error ? error.message : String(error)}`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
function registerCitationTools(server, ctx, track) {
|
|
687
|
+
server.registerTool(
|
|
688
|
+
track(mcpToolName(ctx.productName, "citations_list")),
|
|
689
|
+
{
|
|
690
|
+
title: "List Citations",
|
|
691
|
+
description: "List the citations backing a given story.",
|
|
692
|
+
inputSchema: { storyId: zod.z.string().describe("Story ID") }
|
|
693
|
+
},
|
|
694
|
+
async ({ storyId }) => {
|
|
695
|
+
try {
|
|
696
|
+
const data = await ctx.gql.workspace.request(LIST_CITATIONS, { storyId });
|
|
697
|
+
return jsonResult(data.tcpListCitations);
|
|
698
|
+
} catch (error) {
|
|
699
|
+
ctx.logger.error("theculturepost citations_list failed", { error: String(error) });
|
|
700
|
+
return errorResult(
|
|
701
|
+
`Failed to list citations: ${error instanceof Error ? error.message : String(error)}`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
);
|
|
706
|
+
server.registerTool(
|
|
707
|
+
track(mcpToolName(ctx.productName, "citations_add")),
|
|
708
|
+
{
|
|
709
|
+
title: "Add Citation",
|
|
710
|
+
description: "Attach a citation to a story. New citations default to UNVERIFIED; use citations_verify to corroborate.",
|
|
711
|
+
inputSchema: {
|
|
712
|
+
storyId: zod.z.string().min(1).describe("Story the citation supports"),
|
|
713
|
+
title: zod.z.string().min(1).describe("Title of the cited work"),
|
|
714
|
+
sourceType: SourceTypeEnum.optional().describe("Type of source (default WEB)"),
|
|
715
|
+
author: zod.z.string().optional().describe("Author of the cited work"),
|
|
716
|
+
publisher: zod.z.string().optional().describe("Publisher / outlet"),
|
|
717
|
+
publishedOn: zod.z.string().optional().describe("Publication date (ISO 8601)"),
|
|
718
|
+
url: zod.z.string().optional().describe("Canonical URL of the source"),
|
|
719
|
+
excerpt: zod.z.string().optional().describe("Quoted excerpt supporting the claim"),
|
|
720
|
+
provenance: zod.z.string().optional().describe("Provenance / chain-of-custody note"),
|
|
721
|
+
sourceFileId: zod.z.string().optional().describe("wspace-files-svc File ID for a scan")
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
async ({
|
|
725
|
+
storyId,
|
|
726
|
+
title,
|
|
727
|
+
sourceType,
|
|
728
|
+
author,
|
|
729
|
+
publisher,
|
|
730
|
+
publishedOn,
|
|
731
|
+
url,
|
|
732
|
+
excerpt,
|
|
733
|
+
provenance,
|
|
734
|
+
sourceFileId
|
|
735
|
+
}) => {
|
|
736
|
+
try {
|
|
737
|
+
const input = { storyId, title };
|
|
738
|
+
if (sourceType !== void 0) input.sourceType = sourceType;
|
|
739
|
+
if (author !== void 0) input.author = author;
|
|
740
|
+
if (publisher !== void 0) input.publisher = publisher;
|
|
741
|
+
if (publishedOn !== void 0) input.publishedOn = publishedOn;
|
|
742
|
+
if (url !== void 0) input.url = url;
|
|
743
|
+
if (excerpt !== void 0) input.excerpt = excerpt;
|
|
744
|
+
if (provenance !== void 0) input.provenance = provenance;
|
|
745
|
+
if (sourceFileId !== void 0) input.sourceFileId = sourceFileId;
|
|
746
|
+
const data = await ctx.gql.workspace.request(ADD_CITATION, { input });
|
|
747
|
+
return jsonResult(data.tcpAddCitation);
|
|
748
|
+
} catch (error) {
|
|
749
|
+
ctx.logger.error("theculturepost citations_add failed", { error: String(error) });
|
|
750
|
+
return errorResult(
|
|
751
|
+
`Failed to add citation: ${error instanceof Error ? error.message : String(error)}`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
server.registerTool(
|
|
757
|
+
track(mcpToolName(ctx.productName, "citations_verify")),
|
|
758
|
+
{
|
|
759
|
+
title: "Verify Citation",
|
|
760
|
+
description: "Set the verification status of a citation (CORROBORATED / DISPUTED / RETRACTED / UNVERIFIED).",
|
|
761
|
+
inputSchema: {
|
|
762
|
+
id: zod.z.string().describe("Citation ID"),
|
|
763
|
+
verification: VerificationStatusEnum.describe("New verification status")
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
async ({ id, verification }) => {
|
|
767
|
+
try {
|
|
768
|
+
const data = await ctx.gql.workspace.request(VERIFY_CITATION, { id, verification });
|
|
769
|
+
return jsonResult(data.tcpVerifyCitation);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
ctx.logger.error("theculturepost citations_verify failed", { error: String(error) });
|
|
772
|
+
return errorResult(
|
|
773
|
+
`Failed to verify citation: ${error instanceof Error ? error.message : String(error)}`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
function registerArchiveTools(server, ctx, track) {
|
|
780
|
+
server.registerTool(
|
|
781
|
+
track(mcpToolName(ctx.productName, "archive_get")),
|
|
782
|
+
{
|
|
783
|
+
title: "Get Archive Item",
|
|
784
|
+
description: "Fetch a single archive item (cultural artifact) by ID, including its parent collection.",
|
|
785
|
+
inputSchema: { id: zod.z.string().describe("Archive item ID") }
|
|
786
|
+
},
|
|
787
|
+
async ({ id }) => {
|
|
788
|
+
try {
|
|
789
|
+
const data = await ctx.gql.workspace.request(GET_ARCHIVE_ITEM, { id });
|
|
790
|
+
if (!data.tcpArchiveItem) return errorResult(`Archive item ${id} not found`);
|
|
791
|
+
return jsonResult(data.tcpArchiveItem);
|
|
792
|
+
} catch (error) {
|
|
793
|
+
ctx.logger.error("theculturepost archive_get failed", { error: String(error) });
|
|
794
|
+
return errorResult(
|
|
795
|
+
`Failed to get archive item: ${error instanceof Error ? error.message : String(error)}`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
server.registerTool(
|
|
801
|
+
track(mcpToolName(ctx.productName, "archive_list")),
|
|
802
|
+
{
|
|
803
|
+
title: "List Archive Items",
|
|
804
|
+
description: "List archive items, optionally filtered by access level, collection, and/or kind. Returns a connection ({ items, pageInfo }).",
|
|
805
|
+
inputSchema: {
|
|
806
|
+
accessLevel: AccessLevelEnum.optional().describe("Filter by access level"),
|
|
807
|
+
collectionId: zod.z.string().optional().describe("Filter by parent collection ID"),
|
|
808
|
+
kind: ArchiveItemKindEnum.optional().describe("Filter by artifact kind"),
|
|
809
|
+
...paginationShape
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
async ({ accessLevel, collectionId, kind, cursor, limit, offset }) => {
|
|
813
|
+
try {
|
|
814
|
+
const data = await ctx.gql.workspace.request(LIST_ARCHIVE_ITEMS, {
|
|
815
|
+
accessLevel,
|
|
816
|
+
collectionId,
|
|
817
|
+
kind,
|
|
818
|
+
pagination: buildPagination({ cursor, limit, offset })
|
|
819
|
+
});
|
|
820
|
+
return jsonResult(data.tcpListArchiveItems);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
ctx.logger.error("theculturepost archive_list failed", { error: String(error) });
|
|
823
|
+
return errorResult(
|
|
824
|
+
`Failed to list archive items: ${error instanceof Error ? error.message : String(error)}`
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
server.registerTool(
|
|
830
|
+
track(mcpToolName(ctx.productName, "archive_create")),
|
|
831
|
+
{
|
|
832
|
+
title: "Create Archive Item",
|
|
833
|
+
description: "Add a cultural artifact to the archive. Default kind is DOCUMENT and default access level is PUBLIC. Reference the binary via `fileId` (wspace-files-svc).",
|
|
834
|
+
inputSchema: {
|
|
835
|
+
title: zod.z.string().min(1).describe("Artifact title"),
|
|
836
|
+
kind: ArchiveItemKindEnum.optional().describe("Artifact kind (default DOCUMENT)"),
|
|
837
|
+
accessLevel: AccessLevelEnum.optional().describe("Access level (default PUBLIC)"),
|
|
838
|
+
description: zod.z.string().optional().describe("Artifact description"),
|
|
839
|
+
collectionId: zod.z.string().optional().describe("Parent collection ID"),
|
|
840
|
+
fileId: zod.z.string().optional().describe("wspace-files-svc File ID for the binary"),
|
|
841
|
+
languages: zod.z.array(zod.z.string()).optional().describe("Languages present in the artifact"),
|
|
842
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Free-form tags"),
|
|
843
|
+
origin: zod.z.string().optional().describe("Geographic / cultural origin"),
|
|
844
|
+
provenance: zod.z.string().optional().describe("Provenance / chain-of-custody note"),
|
|
845
|
+
datedAt: zod.z.string().optional().describe("Date the artifact is from (ISO 8601)")
|
|
846
|
+
}
|
|
847
|
+
},
|
|
848
|
+
async ({
|
|
849
|
+
title,
|
|
850
|
+
kind,
|
|
851
|
+
accessLevel,
|
|
852
|
+
description,
|
|
853
|
+
collectionId,
|
|
854
|
+
fileId,
|
|
855
|
+
languages,
|
|
856
|
+
tags,
|
|
857
|
+
origin,
|
|
858
|
+
provenance,
|
|
859
|
+
datedAt
|
|
860
|
+
}) => {
|
|
861
|
+
try {
|
|
862
|
+
const input = { title };
|
|
863
|
+
if (kind !== void 0) input.kind = kind;
|
|
864
|
+
if (accessLevel !== void 0) input.accessLevel = accessLevel;
|
|
865
|
+
if (description !== void 0) input.description = description;
|
|
866
|
+
if (collectionId !== void 0) input.collectionId = collectionId;
|
|
867
|
+
if (fileId !== void 0) input.fileId = fileId;
|
|
868
|
+
if (languages !== void 0) input.languages = languages;
|
|
869
|
+
if (tags !== void 0) input.tags = tags;
|
|
870
|
+
if (origin !== void 0) input.origin = origin;
|
|
871
|
+
if (provenance !== void 0) input.provenance = provenance;
|
|
872
|
+
if (datedAt !== void 0) input.datedAt = datedAt;
|
|
873
|
+
const data = await ctx.gql.workspace.request(CREATE_ARCHIVE_ITEM, { input });
|
|
874
|
+
return jsonResult(data.tcpCreateArchiveItem);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
ctx.logger.error("theculturepost archive_create failed", { error: String(error) });
|
|
877
|
+
return errorResult(
|
|
878
|
+
`Failed to create archive item: ${error instanceof Error ? error.message : String(error)}`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
server.registerTool(
|
|
884
|
+
track(mcpToolName(ctx.productName, "archive_update")),
|
|
885
|
+
{
|
|
886
|
+
title: "Update Archive Item",
|
|
887
|
+
description: "Update an archive item. At least one mutable field must be provided.",
|
|
888
|
+
inputSchema: {
|
|
889
|
+
id: zod.z.string().describe("Archive item ID"),
|
|
890
|
+
title: zod.z.string().optional().describe("Updated title"),
|
|
891
|
+
kind: ArchiveItemKindEnum.optional().describe("Updated artifact kind"),
|
|
892
|
+
accessLevel: AccessLevelEnum.optional().describe("Updated access level"),
|
|
893
|
+
description: zod.z.string().optional().describe("Updated description"),
|
|
894
|
+
collectionId: zod.z.string().optional().describe("Reassign to a collection"),
|
|
895
|
+
fileId: zod.z.string().optional().describe("Updated binary File ID"),
|
|
896
|
+
languages: zod.z.array(zod.z.string()).optional().describe("Updated languages"),
|
|
897
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Updated tags"),
|
|
898
|
+
origin: zod.z.string().optional().describe("Updated origin"),
|
|
899
|
+
provenance: zod.z.string().optional().describe("Updated provenance"),
|
|
900
|
+
datedAt: zod.z.string().optional().describe("Updated artifact date (ISO 8601)")
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
async ({
|
|
904
|
+
id,
|
|
905
|
+
title,
|
|
906
|
+
kind,
|
|
907
|
+
accessLevel,
|
|
908
|
+
description,
|
|
909
|
+
collectionId,
|
|
910
|
+
fileId,
|
|
911
|
+
languages,
|
|
912
|
+
tags,
|
|
913
|
+
origin,
|
|
914
|
+
provenance,
|
|
915
|
+
datedAt
|
|
916
|
+
}) => {
|
|
917
|
+
try {
|
|
918
|
+
const input = {};
|
|
919
|
+
if (title !== void 0) input.title = title;
|
|
920
|
+
if (kind !== void 0) input.kind = kind;
|
|
921
|
+
if (accessLevel !== void 0) input.accessLevel = accessLevel;
|
|
922
|
+
if (description !== void 0) input.description = description;
|
|
923
|
+
if (collectionId !== void 0) input.collectionId = collectionId;
|
|
924
|
+
if (fileId !== void 0) input.fileId = fileId;
|
|
925
|
+
if (languages !== void 0) input.languages = languages;
|
|
926
|
+
if (tags !== void 0) input.tags = tags;
|
|
927
|
+
if (origin !== void 0) input.origin = origin;
|
|
928
|
+
if (provenance !== void 0) input.provenance = provenance;
|
|
929
|
+
if (datedAt !== void 0) input.datedAt = datedAt;
|
|
930
|
+
if (Object.keys(input).length === 0) {
|
|
931
|
+
return errorResult(
|
|
932
|
+
"No updates specified \u2014 provide at least one of title/kind/accessLevel/description/collectionId/fileId/languages/tags/origin/provenance/datedAt"
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
const data = await ctx.gql.workspace.request(UPDATE_ARCHIVE_ITEM, { id, input });
|
|
936
|
+
return jsonResult(data.tcpUpdateArchiveItem);
|
|
937
|
+
} catch (error) {
|
|
938
|
+
ctx.logger.error("theculturepost archive_update failed", { error: String(error) });
|
|
939
|
+
return errorResult(
|
|
940
|
+
`Failed to update archive item: ${error instanceof Error ? error.message : String(error)}`
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
server.registerTool(
|
|
946
|
+
track(mcpToolName(ctx.productName, "archive_archive")),
|
|
947
|
+
{
|
|
948
|
+
title: "Archive Archive Item",
|
|
949
|
+
description: "Archive (soft-delete) an archive item. Returns true on success.",
|
|
950
|
+
inputSchema: { id: zod.z.string().describe("Archive item ID") }
|
|
951
|
+
},
|
|
952
|
+
async ({ id }) => {
|
|
953
|
+
try {
|
|
954
|
+
const data = await ctx.gql.workspace.request(
|
|
955
|
+
ARCHIVE_ARCHIVE_ITEM,
|
|
956
|
+
{ id }
|
|
957
|
+
);
|
|
958
|
+
return jsonResult({ archived: data.tcpArchiveArchiveItem, id });
|
|
959
|
+
} catch (error) {
|
|
960
|
+
ctx.logger.error("theculturepost archive_archive failed", { error: String(error) });
|
|
961
|
+
return errorResult(
|
|
962
|
+
`Failed to archive archive item: ${error instanceof Error ? error.message : String(error)}`
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
function registerCollectionTools(server, ctx, track) {
|
|
969
|
+
server.registerTool(
|
|
970
|
+
track(mcpToolName(ctx.productName, "collections_get")),
|
|
971
|
+
{
|
|
972
|
+
title: "Get Collection",
|
|
973
|
+
description: "Fetch a single collection by ID, including its archive items and oral histories.",
|
|
974
|
+
inputSchema: { id: zod.z.string().describe("Collection ID") }
|
|
975
|
+
},
|
|
976
|
+
async ({ id }) => {
|
|
977
|
+
try {
|
|
978
|
+
const data = await ctx.gql.workspace.request(GET_COLLECTION, { id });
|
|
979
|
+
if (!data.tcpCollection) return errorResult(`Collection ${id} not found`);
|
|
980
|
+
return jsonResult(data.tcpCollection);
|
|
981
|
+
} catch (error) {
|
|
982
|
+
ctx.logger.error("theculturepost collections_get failed", { error: String(error) });
|
|
983
|
+
return errorResult(
|
|
984
|
+
`Failed to get collection: ${error instanceof Error ? error.message : String(error)}`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
);
|
|
989
|
+
server.registerTool(
|
|
990
|
+
track(mcpToolName(ctx.productName, "collections_list")),
|
|
991
|
+
{
|
|
992
|
+
title: "List Collections",
|
|
993
|
+
description: "List the curated collections in the bound workspace.",
|
|
994
|
+
inputSchema: { ...paginationShape }
|
|
995
|
+
},
|
|
996
|
+
async ({ cursor, limit, offset }) => {
|
|
997
|
+
try {
|
|
998
|
+
const data = await ctx.gql.workspace.request(LIST_COLLECTIONS, {
|
|
999
|
+
pagination: buildPagination({ cursor, limit, offset })
|
|
1000
|
+
});
|
|
1001
|
+
return jsonResult(data.tcpListCollections);
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
ctx.logger.error("theculturepost collections_list failed", { error: String(error) });
|
|
1004
|
+
return errorResult(
|
|
1005
|
+
`Failed to list collections: ${error instanceof Error ? error.message : String(error)}`
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
);
|
|
1010
|
+
server.registerTool(
|
|
1011
|
+
track(mcpToolName(ctx.productName, "collections_create")),
|
|
1012
|
+
{
|
|
1013
|
+
title: "Create Collection",
|
|
1014
|
+
description: "Create a curated collection. `slug` must be unique within the workspace.",
|
|
1015
|
+
inputSchema: {
|
|
1016
|
+
name: zod.z.string().min(1).describe("Collection display name"),
|
|
1017
|
+
slug: zod.z.string().min(1).describe("URL slug, unique within the workspace"),
|
|
1018
|
+
description: zod.z.string().optional().describe("Collection description"),
|
|
1019
|
+
tradition: zod.z.string().optional().describe("Associated cultural/religious tradition"),
|
|
1020
|
+
coverFileId: zod.z.string().optional().describe("wspace-files-svc File ID for the cover image")
|
|
1021
|
+
}
|
|
1022
|
+
},
|
|
1023
|
+
async ({ name, slug, description, tradition, coverFileId }) => {
|
|
1024
|
+
try {
|
|
1025
|
+
const input = { name, slug };
|
|
1026
|
+
if (description !== void 0) input.description = description;
|
|
1027
|
+
if (tradition !== void 0) input.tradition = tradition;
|
|
1028
|
+
if (coverFileId !== void 0) input.coverFileId = coverFileId;
|
|
1029
|
+
const data = await ctx.gql.workspace.request(CREATE_COLLECTION, { input });
|
|
1030
|
+
return jsonResult(data.tcpCreateCollection);
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
ctx.logger.error("theculturepost collections_create failed", { error: String(error) });
|
|
1033
|
+
return errorResult(
|
|
1034
|
+
`Failed to create collection: ${error instanceof Error ? error.message : String(error)}`
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
function registerOralHistoryTools(server, ctx, track) {
|
|
1041
|
+
server.registerTool(
|
|
1042
|
+
track(mcpToolName(ctx.productName, "oralhistories_list")),
|
|
1043
|
+
{
|
|
1044
|
+
title: "List Oral Histories",
|
|
1045
|
+
description: "List recorded oral histories, optionally scoped to a single collection.",
|
|
1046
|
+
inputSchema: {
|
|
1047
|
+
collectionId: zod.z.string().optional().describe("Filter by parent collection ID")
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
async ({ collectionId }) => {
|
|
1051
|
+
try {
|
|
1052
|
+
const data = await ctx.gql.workspace.request(LIST_ORAL_HISTORIES, { collectionId });
|
|
1053
|
+
return jsonResult(data.tcpListOralHistories);
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
ctx.logger.error("theculturepost oralhistories_list failed", { error: String(error) });
|
|
1056
|
+
return errorResult(
|
|
1057
|
+
`Failed to list oral histories: ${error instanceof Error ? error.message : String(error)}`
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1062
|
+
server.registerTool(
|
|
1063
|
+
track(mcpToolName(ctx.productName, "oralhistories_record")),
|
|
1064
|
+
{
|
|
1065
|
+
title: "Record Oral History",
|
|
1066
|
+
description: "Register a recorded oral history. Default access level is COMMUNITY. Reference the recording via `recordingFileId` (wspace-files-svc, audio/video).",
|
|
1067
|
+
inputSchema: {
|
|
1068
|
+
title: zod.z.string().min(1).describe("Oral-history title"),
|
|
1069
|
+
accessLevel: AccessLevelEnum.optional().describe("Access level (default COMMUNITY)"),
|
|
1070
|
+
collectionId: zod.z.string().optional().describe("Parent collection ID"),
|
|
1071
|
+
narratorName: zod.z.string().optional().describe("Name of the narrator/storyteller"),
|
|
1072
|
+
language: zod.z.string().optional().describe("Language of the recording"),
|
|
1073
|
+
summary: zod.z.string().optional().describe("Short summary"),
|
|
1074
|
+
transcript: zod.z.string().optional().describe("Full transcript text"),
|
|
1075
|
+
durationSecs: zod.z.number().int().optional().describe("Recording duration in seconds"),
|
|
1076
|
+
recordedAt: zod.z.string().optional().describe("Date recorded (ISO 8601)"),
|
|
1077
|
+
recordingFileId: zod.z.string().optional().describe("wspace-files-svc File ID for the recording")
|
|
1078
|
+
}
|
|
1079
|
+
},
|
|
1080
|
+
async ({
|
|
1081
|
+
title,
|
|
1082
|
+
accessLevel,
|
|
1083
|
+
collectionId,
|
|
1084
|
+
narratorName,
|
|
1085
|
+
language,
|
|
1086
|
+
summary,
|
|
1087
|
+
transcript,
|
|
1088
|
+
durationSecs,
|
|
1089
|
+
recordedAt,
|
|
1090
|
+
recordingFileId
|
|
1091
|
+
}) => {
|
|
1092
|
+
try {
|
|
1093
|
+
const input = { title };
|
|
1094
|
+
if (accessLevel !== void 0) input.accessLevel = accessLevel;
|
|
1095
|
+
if (collectionId !== void 0) input.collectionId = collectionId;
|
|
1096
|
+
if (narratorName !== void 0) input.narratorName = narratorName;
|
|
1097
|
+
if (language !== void 0) input.language = language;
|
|
1098
|
+
if (summary !== void 0) input.summary = summary;
|
|
1099
|
+
if (transcript !== void 0) input.transcript = transcript;
|
|
1100
|
+
if (durationSecs !== void 0) input.durationSecs = durationSecs;
|
|
1101
|
+
if (recordedAt !== void 0) input.recordedAt = recordedAt;
|
|
1102
|
+
if (recordingFileId !== void 0) input.recordingFileId = recordingFileId;
|
|
1103
|
+
const data = await ctx.gql.workspace.request(RECORD_ORAL_HISTORY, { input });
|
|
1104
|
+
return jsonResult(data.tcpRecordOralHistory);
|
|
1105
|
+
} catch (error) {
|
|
1106
|
+
ctx.logger.error("theculturepost oralhistories_record failed", { error: String(error) });
|
|
1107
|
+
return errorResult(
|
|
1108
|
+
`Failed to record oral history: ${error instanceof Error ? error.message : String(error)}`
|
|
1109
|
+
);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
function registerDiscussionTools(server, ctx, track) {
|
|
1115
|
+
server.registerTool(
|
|
1116
|
+
track(mcpToolName(ctx.productName, "discussions_get")),
|
|
1117
|
+
{
|
|
1118
|
+
title: "Get Discussion",
|
|
1119
|
+
description: "Fetch a single discussion by ID, including its replies.",
|
|
1120
|
+
inputSchema: { id: zod.z.string().describe("Discussion ID") }
|
|
1121
|
+
},
|
|
1122
|
+
async ({ id }) => {
|
|
1123
|
+
try {
|
|
1124
|
+
const data = await ctx.gql.workspace.request(GET_DISCUSSION, { id });
|
|
1125
|
+
if (!data.tcpDiscussion) return errorResult(`Discussion ${id} not found`);
|
|
1126
|
+
return jsonResult(data.tcpDiscussion);
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
ctx.logger.error("theculturepost discussions_get failed", { error: String(error) });
|
|
1129
|
+
return errorResult(
|
|
1130
|
+
`Failed to get discussion: ${error instanceof Error ? error.message : String(error)}`
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
1135
|
+
server.registerTool(
|
|
1136
|
+
track(mcpToolName(ctx.productName, "discussions_list")),
|
|
1137
|
+
{
|
|
1138
|
+
title: "List Discussions",
|
|
1139
|
+
description: "List discussions, optionally filtered by moderation status and/or related story. Returns a connection ({ items, pageInfo }).",
|
|
1140
|
+
inputSchema: {
|
|
1141
|
+
status: ModerationStatusEnum.optional().describe("Filter by moderation status"),
|
|
1142
|
+
storyId: zod.z.string().optional().describe("Filter by related story ID"),
|
|
1143
|
+
...paginationShape
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
async ({ status, storyId, cursor, limit, offset }) => {
|
|
1147
|
+
try {
|
|
1148
|
+
const data = await ctx.gql.workspace.request(LIST_DISCUSSIONS, {
|
|
1149
|
+
status,
|
|
1150
|
+
storyId,
|
|
1151
|
+
pagination: buildPagination({ cursor, limit, offset })
|
|
1152
|
+
});
|
|
1153
|
+
return jsonResult(data.tcpListDiscussions);
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
ctx.logger.error("theculturepost discussions_list failed", { error: String(error) });
|
|
1156
|
+
return errorResult(
|
|
1157
|
+
`Failed to list discussions: ${error instanceof Error ? error.message : String(error)}`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
);
|
|
1162
|
+
server.registerTool(
|
|
1163
|
+
track(mcpToolName(ctx.productName, "discussions_open")),
|
|
1164
|
+
{
|
|
1165
|
+
title: "Open Discussion",
|
|
1166
|
+
description: "Open a new community discussion, optionally tied to a story and flagged interfaith.",
|
|
1167
|
+
inputSchema: {
|
|
1168
|
+
title: zod.z.string().min(1).describe("Discussion title"),
|
|
1169
|
+
prompt: zod.z.string().optional().describe("Opening prompt / framing question"),
|
|
1170
|
+
storyId: zod.z.string().optional().describe("Related story ID"),
|
|
1171
|
+
isInterfaith: zod.z.boolean().optional().describe("Flag as an interfaith dialogue (default false)"),
|
|
1172
|
+
tags: zod.z.array(zod.z.string()).optional().describe("Free-form tags")
|
|
1173
|
+
}
|
|
1174
|
+
},
|
|
1175
|
+
async ({ title, prompt, storyId, isInterfaith, tags }) => {
|
|
1176
|
+
try {
|
|
1177
|
+
const input = { title };
|
|
1178
|
+
if (prompt !== void 0) input.prompt = prompt;
|
|
1179
|
+
if (storyId !== void 0) input.storyId = storyId;
|
|
1180
|
+
if (isInterfaith !== void 0) input.isInterfaith = isInterfaith;
|
|
1181
|
+
if (tags !== void 0) input.tags = tags;
|
|
1182
|
+
const data = await ctx.gql.workspace.request(OPEN_DISCUSSION, { input });
|
|
1183
|
+
return jsonResult(data.tcpOpenDiscussion);
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
ctx.logger.error("theculturepost discussions_open failed", { error: String(error) });
|
|
1186
|
+
return errorResult(
|
|
1187
|
+
`Failed to open discussion: ${error instanceof Error ? error.message : String(error)}`
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
server.registerTool(
|
|
1193
|
+
track(mcpToolName(ctx.productName, "discussions_lock")),
|
|
1194
|
+
{
|
|
1195
|
+
title: "Lock Discussion",
|
|
1196
|
+
description: "Lock a discussion thread to prevent further replies.",
|
|
1197
|
+
inputSchema: { id: zod.z.string().describe("Discussion ID") }
|
|
1198
|
+
},
|
|
1199
|
+
async ({ id }) => {
|
|
1200
|
+
try {
|
|
1201
|
+
const data = await ctx.gql.workspace.request(LOCK_DISCUSSION, { id });
|
|
1202
|
+
return jsonResult(data.tcpLockDiscussion);
|
|
1203
|
+
} catch (error) {
|
|
1204
|
+
ctx.logger.error("theculturepost discussions_lock failed", { error: String(error) });
|
|
1205
|
+
return errorResult(
|
|
1206
|
+
`Failed to lock discussion: ${error instanceof Error ? error.message : String(error)}`
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
);
|
|
1211
|
+
}
|
|
1212
|
+
function registerReplyTools(server, ctx, track) {
|
|
1213
|
+
server.registerTool(
|
|
1214
|
+
track(mcpToolName(ctx.productName, "replies_list")),
|
|
1215
|
+
{
|
|
1216
|
+
title: "List Replies",
|
|
1217
|
+
description: "List the replies on a discussion, optionally filtered by moderation status.",
|
|
1218
|
+
inputSchema: {
|
|
1219
|
+
discussionId: zod.z.string().describe("Discussion ID"),
|
|
1220
|
+
status: ModerationStatusEnum.optional().describe("Filter by moderation status")
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
async ({ discussionId, status }) => {
|
|
1224
|
+
try {
|
|
1225
|
+
const data = await ctx.gql.workspace.request(LIST_REPLIES, { discussionId, status });
|
|
1226
|
+
return jsonResult(data.tcpListReplies);
|
|
1227
|
+
} catch (error) {
|
|
1228
|
+
ctx.logger.error("theculturepost replies_list failed", { error: String(error) });
|
|
1229
|
+
return errorResult(
|
|
1230
|
+
`Failed to list replies: ${error instanceof Error ? error.message : String(error)}`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
);
|
|
1235
|
+
server.registerTool(
|
|
1236
|
+
track(mcpToolName(ctx.productName, "replies_post")),
|
|
1237
|
+
{
|
|
1238
|
+
title: "Post Reply",
|
|
1239
|
+
description: "Post a reply to a discussion. Optionally label the contributor\u2019s `perspective` (e.g. a tradition or viewpoint).",
|
|
1240
|
+
inputSchema: {
|
|
1241
|
+
discussionId: zod.z.string().min(1).describe("Discussion being replied to"),
|
|
1242
|
+
body: zod.z.string().min(1).describe("Reply body text"),
|
|
1243
|
+
perspective: zod.z.string().optional().describe("Optional viewpoint/tradition label")
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
async ({ discussionId, body, perspective }) => {
|
|
1247
|
+
try {
|
|
1248
|
+
const input = { discussionId, body };
|
|
1249
|
+
if (perspective !== void 0) input.perspective = perspective;
|
|
1250
|
+
const data = await ctx.gql.workspace.request(POST_REPLY, { input });
|
|
1251
|
+
return jsonResult(data.tcpPostReply);
|
|
1252
|
+
} catch (error) {
|
|
1253
|
+
ctx.logger.error("theculturepost replies_post failed", { error: String(error) });
|
|
1254
|
+
return errorResult(
|
|
1255
|
+
`Failed to post reply: ${error instanceof Error ? error.message : String(error)}`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
);
|
|
1260
|
+
server.registerTool(
|
|
1261
|
+
track(mcpToolName(ctx.productName, "replies_moderate")),
|
|
1262
|
+
{
|
|
1263
|
+
title: "Moderate Reply",
|
|
1264
|
+
description: "Moderate a reply \u2014 set its status to APPROVED, FLAGGED, PENDING, or REMOVED.",
|
|
1265
|
+
inputSchema: {
|
|
1266
|
+
id: zod.z.string().describe("Reply ID"),
|
|
1267
|
+
status: ModerationStatusEnum.describe("New moderation status")
|
|
1268
|
+
}
|
|
1269
|
+
},
|
|
1270
|
+
async ({ id, status }) => {
|
|
1271
|
+
try {
|
|
1272
|
+
const data = await ctx.gql.workspace.request(MODERATE_REPLY, { id, status });
|
|
1273
|
+
return jsonResult(data.tcpModerateReply);
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
ctx.logger.error("theculturepost replies_moderate failed", { error: String(error) });
|
|
1276
|
+
return errorResult(
|
|
1277
|
+
`Failed to moderate reply: ${error instanceof Error ? error.message : String(error)}`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/modules/theculturepost/tools/system.ts
|
|
1285
|
+
function registerSystemTools(server, ctx, track) {
|
|
1286
|
+
server.registerTool(
|
|
1287
|
+
track(mcpToolName(ctx.productName, "health")),
|
|
1288
|
+
{
|
|
1289
|
+
title: "TheCulturePost Health Check",
|
|
1290
|
+
description: "Public health probe for the TheCulturePost subgraph (tcpHealth). Requires no authentication.",
|
|
1291
|
+
inputSchema: {}
|
|
1292
|
+
},
|
|
1293
|
+
async () => {
|
|
1294
|
+
try {
|
|
1295
|
+
const data = await ctx.gql.workspace.request(HEALTH, {});
|
|
1296
|
+
return jsonResult({ ok: true, status: data.tcpHealth });
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
ctx.logger.error("theculturepost health failed", { error: String(error) });
|
|
1299
|
+
return errorResult(
|
|
1300
|
+
`TheCulturePost health check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/modules/theculturepost/resources.ts
|
|
1308
|
+
function registerTheculturepostResources(server, ctx, track) {
|
|
1309
|
+
const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
|
|
1310
|
+
const storiesUri = track(mcpResourceUri(ctx.productName, "stories"));
|
|
1311
|
+
const topicsUri = track(mcpResourceUri(ctx.productName, "topics"));
|
|
1312
|
+
const collectionsUri = track(mcpResourceUri(ctx.productName, "collections"));
|
|
1313
|
+
server.registerResource(
|
|
1314
|
+
"theculturepost-schema",
|
|
1315
|
+
schemaUri,
|
|
1316
|
+
{
|
|
1317
|
+
title: "TheCulturePost Domain Schema",
|
|
1318
|
+
description: "Overview of the TheCulturePost domain \u2014 entities, enums, and the per-entity tool surface.",
|
|
1319
|
+
mimeType: "text/plain"
|
|
1320
|
+
},
|
|
1321
|
+
async () => {
|
|
1322
|
+
ctx.logger.debug(`Resource ${schemaUri} requested`);
|
|
1323
|
+
const schema = `# TheCulturePost Domain (Overview)
|
|
1324
|
+
|
|
1325
|
+
Culture / religion / values / history platform \u2014 well-sourced stories, topic
|
|
1326
|
+
timelines, citable archives + oral histories, respectful community discussion.
|
|
1327
|
+
|
|
1328
|
+
## Entities
|
|
1329
|
+
- TcpStory \u2014 an editorial story (has citations, belongs to a topic)
|
|
1330
|
+
- TcpTopic \u2014 a thematic hub grouping stories
|
|
1331
|
+
- TcpCitation \u2014 a cited source backing a story claim
|
|
1332
|
+
- TcpArchiveItem \u2014 a cultural artifact (binary referenced via wspace-files-svc File)
|
|
1333
|
+
- TcpCollection \u2014 a curated grouping of archive items + oral histories
|
|
1334
|
+
- TcpOralHistory \u2014 recorded first-person testimony (recording via File)
|
|
1335
|
+
- TcpDiscussion \u2014 a respectful community thread (optionally tied to a story)
|
|
1336
|
+
- TcpReply \u2014 a moderated contribution to a discussion
|
|
1337
|
+
|
|
1338
|
+
## Enums
|
|
1339
|
+
- TcpStoryStatus: IDEA, DRAFT, IN_REVIEW, PUBLISHED, ARCHIVED
|
|
1340
|
+
- TcpStoryFormat: ARTICLE, ESSAY, EXPLAINER, ORAL_HISTORY, PHOTO_STORY, TIMELINE
|
|
1341
|
+
- TcpTopicCategory: CULTURE_AND_IDENTITY, FESTIVALS_AND_RITUALS, HERITAGE_AND_ARCHIVES, HISTORY, MIGRATION_AND_DIASPORA, ORAL_HISTORIES, PHILOSOPHY_AND_THOUGHT, RELIGION_AND_FAITH, VALUES_AND_ETHICS
|
|
1342
|
+
- TcpSourceType: ACADEMIC, ARCHIVAL_RECORD, ARTIFACT, BOOK, INTERVIEW, PERIODICAL, PRIMARY_DOCUMENT, WEB
|
|
1343
|
+
- TcpVerificationStatus: UNVERIFIED, CORROBORATED, DISPUTED, RETRACTED
|
|
1344
|
+
- TcpArchiveItemKind: AUDIO, DOCUMENT, MANUSCRIPT, MAP, OBJECT, PHOTOGRAPH, VIDEO
|
|
1345
|
+
- TcpAccessLevel: PUBLIC, COMMUNITY, RESTRICTED, EMBARGOED
|
|
1346
|
+
- TcpModerationStatus: PENDING, APPROVED, FLAGGED, REMOVED
|
|
1347
|
+
|
|
1348
|
+
## Tool groups
|
|
1349
|
+
stories, topics, citations, archive, collections, oralhistories, discussions, replies, health.
|
|
1350
|
+
All run against the WORKSPACE gateway (workspace-scoped) except the public health probe.
|
|
1351
|
+
`;
|
|
1352
|
+
return {
|
|
1353
|
+
contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
);
|
|
1357
|
+
server.registerResource(
|
|
1358
|
+
"theculturepost-stories",
|
|
1359
|
+
storiesUri,
|
|
1360
|
+
{
|
|
1361
|
+
title: "TheCulturePost Stories",
|
|
1362
|
+
description: "The first page of stories in the bound workspace.",
|
|
1363
|
+
mimeType: "application/json"
|
|
1364
|
+
},
|
|
1365
|
+
async () => {
|
|
1366
|
+
ctx.logger.debug(`Resource ${storiesUri} requested`);
|
|
1367
|
+
try {
|
|
1368
|
+
const data = await ctx.gql.workspace.request(LIST_STORIES, {
|
|
1369
|
+
pagination: { limit: 50, offset: 0 }
|
|
1370
|
+
});
|
|
1371
|
+
return {
|
|
1372
|
+
contents: [
|
|
1373
|
+
{
|
|
1374
|
+
uri: storiesUri,
|
|
1375
|
+
text: JSON.stringify(data.tcpListStories, null, 2),
|
|
1376
|
+
mimeType: "application/json"
|
|
1377
|
+
}
|
|
1378
|
+
]
|
|
1379
|
+
};
|
|
1380
|
+
} catch (error) {
|
|
1381
|
+
return {
|
|
1382
|
+
contents: [
|
|
1383
|
+
{
|
|
1384
|
+
uri: storiesUri,
|
|
1385
|
+
text: JSON.stringify({
|
|
1386
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1387
|
+
}),
|
|
1388
|
+
mimeType: "application/json"
|
|
1389
|
+
}
|
|
1390
|
+
]
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
);
|
|
1395
|
+
server.registerResource(
|
|
1396
|
+
"theculturepost-topics",
|
|
1397
|
+
topicsUri,
|
|
1398
|
+
{
|
|
1399
|
+
title: "TheCulturePost Topic Directory",
|
|
1400
|
+
description: "The first page of topics in the bound workspace.",
|
|
1401
|
+
mimeType: "application/json"
|
|
1402
|
+
},
|
|
1403
|
+
async () => {
|
|
1404
|
+
ctx.logger.debug(`Resource ${topicsUri} requested`);
|
|
1405
|
+
try {
|
|
1406
|
+
const data = await ctx.gql.workspace.request(LIST_TOPICS, {
|
|
1407
|
+
pagination: { limit: 50, offset: 0 }
|
|
1408
|
+
});
|
|
1409
|
+
return {
|
|
1410
|
+
contents: [
|
|
1411
|
+
{
|
|
1412
|
+
uri: topicsUri,
|
|
1413
|
+
text: JSON.stringify(data.tcpListTopics, null, 2),
|
|
1414
|
+
mimeType: "application/json"
|
|
1415
|
+
}
|
|
1416
|
+
]
|
|
1417
|
+
};
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
return {
|
|
1420
|
+
contents: [
|
|
1421
|
+
{
|
|
1422
|
+
uri: topicsUri,
|
|
1423
|
+
text: JSON.stringify({
|
|
1424
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1425
|
+
}),
|
|
1426
|
+
mimeType: "application/json"
|
|
1427
|
+
}
|
|
1428
|
+
]
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
);
|
|
1433
|
+
server.registerResource(
|
|
1434
|
+
"theculturepost-collections",
|
|
1435
|
+
collectionsUri,
|
|
1436
|
+
{
|
|
1437
|
+
title: "TheCulturePost Collection Directory",
|
|
1438
|
+
description: "The curated collections in the bound workspace.",
|
|
1439
|
+
mimeType: "application/json"
|
|
1440
|
+
},
|
|
1441
|
+
async () => {
|
|
1442
|
+
ctx.logger.debug(`Resource ${collectionsUri} requested`);
|
|
1443
|
+
try {
|
|
1444
|
+
const data = await ctx.gql.workspace.request(
|
|
1445
|
+
LIST_COLLECTIONS,
|
|
1446
|
+
{ pagination: { limit: 50, offset: 0 } }
|
|
1447
|
+
);
|
|
1448
|
+
return {
|
|
1449
|
+
contents: [
|
|
1450
|
+
{
|
|
1451
|
+
uri: collectionsUri,
|
|
1452
|
+
text: JSON.stringify(data.tcpListCollections, null, 2),
|
|
1453
|
+
mimeType: "application/json"
|
|
1454
|
+
}
|
|
1455
|
+
]
|
|
1456
|
+
};
|
|
1457
|
+
} catch (error) {
|
|
1458
|
+
return {
|
|
1459
|
+
contents: [
|
|
1460
|
+
{
|
|
1461
|
+
uri: collectionsUri,
|
|
1462
|
+
text: JSON.stringify({
|
|
1463
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1464
|
+
}),
|
|
1465
|
+
mimeType: "application/json"
|
|
1466
|
+
}
|
|
1467
|
+
]
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
function registerTheculturepostPrompts(server, ctx, track) {
|
|
1474
|
+
const p = ctx.productName;
|
|
1475
|
+
const publishingName = track(mcpPromptName(p, "story-publishing"));
|
|
1476
|
+
const archiveName = track(mcpPromptName(p, "archive-curation"));
|
|
1477
|
+
server.registerPrompt(
|
|
1478
|
+
publishingName,
|
|
1479
|
+
{
|
|
1480
|
+
title: "Story Publishing",
|
|
1481
|
+
description: "Guide for taking a TheCulturePost story from idea to publication: pick/create a topic, draft the story, attach + verify citations, then transition through the editorial lifecycle.",
|
|
1482
|
+
argsSchema: {
|
|
1483
|
+
title: zod.z.string().optional().describe("Story headline"),
|
|
1484
|
+
topicId: zod.z.string().optional().describe("Owning topic ID, if known")
|
|
1485
|
+
}
|
|
1486
|
+
},
|
|
1487
|
+
({ title, topicId }) => {
|
|
1488
|
+
ctx.logger.debug(`Prompt ${publishingName} called`, { title, topicId });
|
|
1489
|
+
const titleNote = title ? `Use the headline "${title}".` : "Ask the user for the story headline.";
|
|
1490
|
+
const topicNote = topicId ? `File the story under topic ${topicId}.` : `First find or create a topic with ${p}_topics_list / ${p}_topics_create.`;
|
|
1491
|
+
return {
|
|
1492
|
+
messages: [
|
|
1493
|
+
{
|
|
1494
|
+
role: "user",
|
|
1495
|
+
content: {
|
|
1496
|
+
type: "text",
|
|
1497
|
+
text: `Help me publish a well-sourced TheCulturePost story. ${titleNote}
|
|
1498
|
+
|
|
1499
|
+
1. **Topic**: ${topicNote}
|
|
1500
|
+
2. **Draft**: Use ${p}_stories_create. Provide a unique slug, a summary, a format (ARTICLE/ESSAY/EXPLAINER/PHOTO_STORY/TIMELINE/ORAL_HISTORY), and the body. It starts in DRAFT.
|
|
1501
|
+
3. **Cite**: For every factual claim, attach a source with ${p}_citations_add (set a sourceType and url/excerpt). Sourcing is non-negotiable here.
|
|
1502
|
+
4. **Verify**: Use ${p}_citations_verify to mark each citation CORROBORATED (or DISPUTED/RETRACTED). Review with ${p}_citations_list.
|
|
1503
|
+
5. **Review \u2192 Publish**: Use ${p}_stories_transition to move DRAFT \u2192 IN_REVIEW, then \u2192 PUBLISHED once citations check out.
|
|
1504
|
+
|
|
1505
|
+
Show results at each step and confirm before publishing.`
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
]
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
);
|
|
1512
|
+
server.registerPrompt(
|
|
1513
|
+
archiveName,
|
|
1514
|
+
{
|
|
1515
|
+
title: "Archive Curation",
|
|
1516
|
+
description: "Workflow for curating a cultural archive: create a collection, add archive items and oral histories, then open a respectful community discussion.",
|
|
1517
|
+
argsSchema: {
|
|
1518
|
+
collectionId: zod.z.string().optional().describe("Collection ID to curate, if it exists")
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
({ collectionId }) => {
|
|
1522
|
+
ctx.logger.debug(`Prompt ${archiveName} called`, { collectionId });
|
|
1523
|
+
const collectionNote = collectionId ? `Curate collection "${collectionId}".` : `First create a collection with ${p}_collections_create (unique slug).`;
|
|
1524
|
+
return {
|
|
1525
|
+
messages: [
|
|
1526
|
+
{
|
|
1527
|
+
role: "user",
|
|
1528
|
+
content: {
|
|
1529
|
+
type: "text",
|
|
1530
|
+
text: `Help me curate a TheCulturePost archive collection. ${collectionNote}
|
|
1531
|
+
|
|
1532
|
+
1. **Collection**: Use ${p}_collections_create (or ${p}_collections_get to inspect an existing one).
|
|
1533
|
+
2. **Archive Items**: Use ${p}_archive_create for each artifact \u2014 set a kind (PHOTOGRAPH/MANUSCRIPT/MAP/OBJECT/AUDIO/VIDEO/DOCUMENT), an accessLevel, provenance/origin, and reference the binary via fileId. List with ${p}_archive_list filtered by collectionId.
|
|
1534
|
+
3. **Oral Histories**: Use ${p}_oralhistories_record for recorded testimony (narratorName, language, transcript, recordingFileId). Review with ${p}_oralhistories_list.
|
|
1535
|
+
4. **Discussion**: Use ${p}_discussions_open to invite respectful community dialogue around the collection or a related story; moderate replies with ${p}_replies_moderate.
|
|
1536
|
+
|
|
1537
|
+
Summarize what was added after each step.`
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
]
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// src/modules/theculturepost/index.ts
|
|
1547
|
+
var theculturepostModule = {
|
|
1548
|
+
id: "theculturepost",
|
|
1549
|
+
version: "0.0.1",
|
|
1550
|
+
displayName: "TheCulturePost",
|
|
1551
|
+
dependsOn: ["auth"],
|
|
1552
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
1553
|
+
register(server, ctx) {
|
|
1554
|
+
const tools = [];
|
|
1555
|
+
const resources = [];
|
|
1556
|
+
const prompts = [];
|
|
1557
|
+
const trackTool = (n) => {
|
|
1558
|
+
tools.push(n);
|
|
1559
|
+
return n;
|
|
1560
|
+
};
|
|
1561
|
+
const trackResource = (uri) => {
|
|
1562
|
+
resources.push(uri);
|
|
1563
|
+
return uri;
|
|
1564
|
+
};
|
|
1565
|
+
const trackPrompt = (n) => {
|
|
1566
|
+
prompts.push(n);
|
|
1567
|
+
return n;
|
|
1568
|
+
};
|
|
1569
|
+
registerStoryTools(server, ctx, trackTool);
|
|
1570
|
+
registerTopicTools(server, ctx, trackTool);
|
|
1571
|
+
registerCitationTools(server, ctx, trackTool);
|
|
1572
|
+
registerArchiveTools(server, ctx, trackTool);
|
|
1573
|
+
registerCollectionTools(server, ctx, trackTool);
|
|
1574
|
+
registerOralHistoryTools(server, ctx, trackTool);
|
|
1575
|
+
registerDiscussionTools(server, ctx, trackTool);
|
|
1576
|
+
registerReplyTools(server, ctx, trackTool);
|
|
1577
|
+
registerSystemTools(server, ctx, trackTool);
|
|
1578
|
+
registerTheculturepostResources(server, ctx, trackResource);
|
|
1579
|
+
registerTheculturepostPrompts(server, ctx, trackPrompt);
|
|
1580
|
+
return { tools, resources, prompts };
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
var theculturepost_default = theculturepostModule;
|
|
1584
|
+
|
|
1585
|
+
module.exports = theculturepost_default;
|
|
1586
|
+
//# sourceMappingURL=index.cjs.map
|
|
1587
|
+
//# sourceMappingURL=index.cjs.map
|