@iflow-mcp/graphlit-graphlit-mcp-server 1.0.1

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.
@@ -0,0 +1,533 @@
1
+ import { Graphlit } from "graphlit-client";
2
+ import { ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { ContentTypes, EntityState, } from "graphlit-client/dist/generated/graphql-types";
4
+ export function registerResources(server) {
5
+ server.resource("Conversations list: Returns list of conversation resources.", new ResourceTemplate("conversations://", {
6
+ list: async (extra) => {
7
+ const client = new Graphlit();
8
+ const filter = {};
9
+ try {
10
+ const response = await client.queryConversations(filter);
11
+ return {
12
+ resources: (response.conversations?.results || [])
13
+ .filter((content) => content !== null)
14
+ .map((conversation) => ({
15
+ name: conversation.name,
16
+ uri: `conversations://${conversation.id}`,
17
+ mimeType: "text/markdown",
18
+ })),
19
+ };
20
+ }
21
+ catch (error) {
22
+ console.error("Error fetching conversation list:", error);
23
+ return { resources: [] };
24
+ }
25
+ },
26
+ }), async (uri, variables) => {
27
+ return {
28
+ contents: [],
29
+ };
30
+ });
31
+ server.resource("Conversation: Returns LLM conversation messages. Accepts conversation resource URI, i.e. conversations://{id}, where 'id' is a conversation identifier.", new ResourceTemplate("conversations://{id}", { list: undefined }), async (uri, variables) => {
32
+ const id = variables.id;
33
+ const client = new Graphlit();
34
+ try {
35
+ const response = await client.getConversation(id);
36
+ const content = response.conversation;
37
+ return {
38
+ contents: [
39
+ {
40
+ uri: uri.toString(),
41
+ text: formatConversation(response),
42
+ mimeType: "text/markdown",
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ catch (error) {
48
+ console.error("Error fetching conversation:", error);
49
+ return {
50
+ contents: [],
51
+ };
52
+ }
53
+ });
54
+ server.resource("Feeds: Returns list of feed resources.", new ResourceTemplate("feeds://", {
55
+ list: async (extra) => {
56
+ const client = new Graphlit();
57
+ try {
58
+ const response = await client.queryFeeds();
59
+ return {
60
+ resources: (response.feeds?.results || [])
61
+ .filter((feed) => feed !== null)
62
+ .map((feed) => ({
63
+ name: feed.name,
64
+ uri: `feeds://${feed.id}`,
65
+ })),
66
+ };
67
+ }
68
+ catch (error) {
69
+ console.error("Error fetching feed list:", error);
70
+ return { resources: [] };
71
+ }
72
+ },
73
+ }), async (uri, variables) => {
74
+ return {
75
+ contents: [],
76
+ };
77
+ });
78
+ server.resource("Feed: Returns feed metadata. Accepts content resource URI, i.e. feeds://{id}, where 'id' is a feed identifier.", new ResourceTemplate("feeds://{id}", { list: undefined }), async (uri, variables) => {
79
+ const id = variables.id;
80
+ const client = new Graphlit();
81
+ try {
82
+ const response = await client.getFeed(id);
83
+ return {
84
+ contents: [
85
+ {
86
+ uri: uri.toString(),
87
+ text: JSON.stringify({
88
+ id: response.feed?.id,
89
+ name: response.feed?.name,
90
+ type: response.feed?.type,
91
+ readCount: response.feed?.readCount,
92
+ creationDate: response.feed?.creationDate,
93
+ lastReadDate: response.feed?.lastReadDate,
94
+ state: response.feed?.state,
95
+ error: response.feed?.error,
96
+ }, null, 2),
97
+ mimeType: "application/json",
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ catch (error) {
103
+ console.error("Error fetching feed:", error);
104
+ return {
105
+ contents: [],
106
+ };
107
+ }
108
+ });
109
+ server.resource("Collections: Returns list of collection resources.", new ResourceTemplate("collections://", {
110
+ list: async (extra) => {
111
+ const client = new Graphlit();
112
+ try {
113
+ const response = await client.queryCollections();
114
+ return {
115
+ resources: (response.collections?.results || [])
116
+ .filter((collection) => collection !== null)
117
+ .map((collection) => ({
118
+ name: collection.name,
119
+ uri: `collections://${collection.id}`,
120
+ })),
121
+ };
122
+ }
123
+ catch (error) {
124
+ console.error("Error fetching collection list:", error);
125
+ return { resources: [] };
126
+ }
127
+ },
128
+ }), async (uri, variables) => {
129
+ return {
130
+ contents: [],
131
+ };
132
+ });
133
+ server.resource("Collection: Returns collection metadata and list of content resources. Accepts collection resource URI, i.e. collections://{id}, where 'id' is a collection identifier.", new ResourceTemplate("collections://{id}", { list: undefined }), async (uri, variables) => {
134
+ const id = variables.id;
135
+ const client = new Graphlit();
136
+ try {
137
+ const response = await client.getCollection(id);
138
+ return {
139
+ contents: [
140
+ {
141
+ uri: uri.toString(),
142
+ text: JSON.stringify({
143
+ id: response.collection?.id,
144
+ name: response.collection?.name,
145
+ contents: (response.collection?.contents || [])
146
+ .filter((content) => content !== null)
147
+ .map((content) => `contents://${content.id}`),
148
+ }, null, 2),
149
+ mimeType: "application/json",
150
+ },
151
+ ],
152
+ };
153
+ }
154
+ catch (error) {
155
+ console.error("Error fetching collection:", error);
156
+ return {
157
+ contents: [],
158
+ };
159
+ }
160
+ });
161
+ server.resource("Contents list: Returns list of content resources.", new ResourceTemplate("contents://", {
162
+ list: async (extra) => {
163
+ const client = new Graphlit();
164
+ const filter = {
165
+ states: [EntityState.Finished], // filter on finished contents only
166
+ };
167
+ try {
168
+ const response = await client.queryContents(filter);
169
+ return {
170
+ resources: (response.contents?.results || [])
171
+ .filter((content) => content !== null)
172
+ .map((content) => ({
173
+ name: content.name,
174
+ description: content.description || "",
175
+ uri: `contents://${content.id}`,
176
+ mimeType: content.mimeType || "text/markdown",
177
+ })),
178
+ };
179
+ }
180
+ catch (error) {
181
+ console.error("Error fetching content list:", error);
182
+ return { resources: [] };
183
+ }
184
+ },
185
+ }), async (uri, variables) => {
186
+ return {
187
+ contents: [],
188
+ };
189
+ });
190
+ server.resource("Content: Returns content metadata and complete Markdown text. Accepts content resource URI, i.e. contents://{id}, where 'id' is a content identifier.", new ResourceTemplate("contents://{id}", { list: undefined }), async (uri, variables) => {
191
+ const id = variables.id;
192
+ const client = new Graphlit();
193
+ try {
194
+ const response = await client.getContent(id);
195
+ const content = response.content;
196
+ return {
197
+ contents: [
198
+ {
199
+ uri: uri.toString(),
200
+ text: formatContent(response),
201
+ mimeType: "text/markdown",
202
+ },
203
+ ],
204
+ };
205
+ }
206
+ catch (error) {
207
+ console.error("Error fetching content:", error);
208
+ return {
209
+ contents: [],
210
+ };
211
+ }
212
+ });
213
+ server.resource("Workflows: Returns list of workflow resources.", new ResourceTemplate("workflows://", {
214
+ list: async (extra) => {
215
+ const client = new Graphlit();
216
+ try {
217
+ const response = await client.queryWorkflows();
218
+ return {
219
+ resources: (response.workflows?.results || [])
220
+ .filter((workflow) => workflow !== null)
221
+ .map((workflow) => ({
222
+ name: workflow.name,
223
+ uri: `workflows://${workflow.id}`,
224
+ })),
225
+ };
226
+ }
227
+ catch (error) {
228
+ console.error("Error fetching workflow list:", error);
229
+ return { resources: [] };
230
+ }
231
+ },
232
+ }), async (uri, variables) => {
233
+ return {
234
+ contents: [],
235
+ };
236
+ });
237
+ server.resource("Workflow: Returns workflow metadata. Accepts workflow resource URI, i.e. workflows://{id}, where 'id' is a workflow identifier.", new ResourceTemplate("workflows://{id}", { list: undefined }), async (uri, variables) => {
238
+ const id = variables.id;
239
+ const client = new Graphlit();
240
+ try {
241
+ const response = await client.getWorkflow(id);
242
+ return {
243
+ contents: [
244
+ {
245
+ uri: uri.toString(),
246
+ text: JSON.stringify(response.workflow, null, 2),
247
+ mimeType: "application/json",
248
+ },
249
+ ],
250
+ };
251
+ }
252
+ catch (error) {
253
+ console.error("Error fetching workflow:", error);
254
+ return {
255
+ contents: [],
256
+ };
257
+ }
258
+ });
259
+ server.resource("Specifications: Returns list of specification resources.", new ResourceTemplate("specifications://", {
260
+ list: async (extra) => {
261
+ const client = new Graphlit();
262
+ try {
263
+ const response = await client.querySpecifications();
264
+ return {
265
+ resources: (response.specifications?.results || [])
266
+ .filter((specification) => specification !== null)
267
+ .map((specification) => ({
268
+ name: specification.name,
269
+ uri: `specifications://${specification.id}`,
270
+ })),
271
+ };
272
+ }
273
+ catch (error) {
274
+ console.error("Error fetching specification list:", error);
275
+ return { resources: [] };
276
+ }
277
+ },
278
+ }), async (uri, variables) => {
279
+ return {
280
+ contents: [],
281
+ };
282
+ });
283
+ server.resource("Specification: Returns specification metadata. Accepts specification resource URI, i.e. specifications://{id}, where 'id' is a specification identifier.", new ResourceTemplate("specifications://{id}", { list: undefined }), async (uri, variables) => {
284
+ const id = variables.id;
285
+ const client = new Graphlit();
286
+ try {
287
+ const response = await client.getSpecification(id);
288
+ return {
289
+ contents: [
290
+ {
291
+ uri: uri.toString(),
292
+ text: JSON.stringify(response.specification, null, 2),
293
+ mimeType: "application/json",
294
+ },
295
+ ],
296
+ };
297
+ }
298
+ catch (error) {
299
+ console.error("Error fetching specification:", error);
300
+ return {
301
+ contents: [],
302
+ };
303
+ }
304
+ });
305
+ server.resource("Project: Returns current Graphlit project metadata including credits and LLM tokens used in the last day, available quota, and default content workflow. Accepts project resource URI, i.e. projects://{id}, where 'id' is a project identifier.", new ResourceTemplate("projects://", { list: undefined }), async (uri, variables) => {
306
+ const id = variables.id;
307
+ const client = new Graphlit();
308
+ try {
309
+ const startDate = new Date(Date.now() - 24 * 60 * 60 * 1000); // 1 day ago
310
+ const duration = "P1D"; // ISO duration for 1 day
311
+ const cresponse = await client.queryProjectCredits(startDate.toISOString(), duration);
312
+ const credits = cresponse?.credits;
313
+ const tresponse = await client.queryProjectTokens(startDate.toISOString(), duration);
314
+ const tokens = tresponse?.tokens;
315
+ const response = await client.getProject();
316
+ return {
317
+ contents: [
318
+ {
319
+ uri: uri.toString(),
320
+ text: JSON.stringify({
321
+ name: response.project?.name,
322
+ workflow: response.project?.workflow,
323
+ quota: response.project?.quota,
324
+ credits: credits,
325
+ tokens: tokens,
326
+ }, null, 2),
327
+ mimeType: "application/json",
328
+ },
329
+ ],
330
+ };
331
+ }
332
+ catch (error) {
333
+ console.error("Error fetching project:", error);
334
+ return {
335
+ contents: [],
336
+ };
337
+ }
338
+ });
339
+ }
340
+ function formatConversation(response) {
341
+ const results = [];
342
+ const conversation = response.conversation;
343
+ if (!conversation) {
344
+ return "";
345
+ }
346
+ // Basic conversation details
347
+ results.push(`**Conversation ID:** ${conversation.id}`);
348
+ // Messages
349
+ if (conversation.messages?.length) {
350
+ conversation.messages.forEach((message) => {
351
+ results.push(`${message?.role}:\n${message?.message}` || "");
352
+ if (message?.citations?.length) {
353
+ message.citations.forEach((citation) => {
354
+ results.push(`**Cited Source [${citation?.index}]**: contents://${citation?.content?.id}`);
355
+ results.push(`**Cited Text**:\n${citation?.text || ""}`);
356
+ });
357
+ }
358
+ results.push("\n---\n");
359
+ });
360
+ }
361
+ return results.join("\n");
362
+ }
363
+ function formatContent(response) {
364
+ const results = [];
365
+ const content = response.content;
366
+ if (!content) {
367
+ return "";
368
+ }
369
+ // Basic content details
370
+ results.push(`**Content ID:** ${content.id}`);
371
+ if (content.type === ContentTypes.File) {
372
+ results.push(`**File Type:** [${content.fileType}]`);
373
+ results.push(`**File Name:** ${content.fileName}`);
374
+ }
375
+ else {
376
+ results.push(`**Type:** [${content.type}]`);
377
+ if (content.type !== ContentTypes.Page &&
378
+ content.type !== ContentTypes.Email) {
379
+ results.push(`**Name:** ${content.name}`);
380
+ }
381
+ }
382
+ // Optional metadata
383
+ //
384
+ // REVIEW: Not sure if source URI is useful for MCP client
385
+ //
386
+ //if (content.uri) results.push(`**URI:** ${content.uri}`);
387
+ if (content.masterUri)
388
+ results.push(`**Downloadable Original:** ${content.masterUri}`);
389
+ if (content.imageUri)
390
+ results.push(`**Downloadable Image:** ${content.imageUri}`);
391
+ if (content.audioUri)
392
+ results.push(`**Downloadable Audio:** ${content.audioUri}`);
393
+ if (content.creationDate)
394
+ results.push(`**Ingestion Date:** ${content.creationDate}`);
395
+ if (content.originalDate)
396
+ results.push(`**Author Date:** ${content.originalDate}`);
397
+ // Issue details
398
+ if (content.issue) {
399
+ const issue = content.issue;
400
+ const issueAttributes = [
401
+ ["Title", issue.title],
402
+ ["Identifier", issue.identifier],
403
+ ["Type", issue.type],
404
+ ["Project", issue.project],
405
+ ["Team", issue.team],
406
+ ["Status", issue.status],
407
+ ["Priority", issue.priority],
408
+ ];
409
+ results.push(...issueAttributes
410
+ .filter(([_, value]) => value)
411
+ .map(([label, value]) => `**${label}:** ${value}`));
412
+ if (issue.labels?.length) {
413
+ results.push(`**Labels:** ${issue.labels.join(", ")}`);
414
+ }
415
+ }
416
+ // Email details
417
+ if (content.email) {
418
+ const email = content.email;
419
+ const formatRecipient = (r) => `${r.name} <${r.email}>`;
420
+ const emailAttributes = [
421
+ ["Subject", email.subject],
422
+ ["Sensitivity", email.sensitivity],
423
+ ["Priority", email.priority],
424
+ ["Importance", email.importance],
425
+ ["Labels", email.labels?.join(", ")],
426
+ ["To", email.to?.map(formatRecipient).join(", ")],
427
+ ["From", email.from?.map(formatRecipient).join(", ")],
428
+ ["CC", email.cc?.map(formatRecipient).join(", ")],
429
+ ["BCC", email.bcc?.map(formatRecipient).join(", ")],
430
+ ];
431
+ results.push(...emailAttributes
432
+ .filter(([_, value]) => value)
433
+ .map(([label, value]) => `**${label}:** ${value}`));
434
+ }
435
+ // Document details
436
+ if (content.document) {
437
+ const doc = content.document;
438
+ if (doc.title)
439
+ results.push(`**Title:** ${doc.title}`);
440
+ if (doc.author)
441
+ results.push(`**Author:** ${doc.author}`);
442
+ }
443
+ // Audio details
444
+ if (content.audio) {
445
+ const audio = content.audio;
446
+ if (audio.title)
447
+ results.push(`**Title:** ${audio.title}`);
448
+ if (audio.author)
449
+ results.push(`**Host:** ${audio.author}`);
450
+ if (audio.episode)
451
+ results.push(`**Episode:** ${audio.episode}`);
452
+ if (audio.series)
453
+ results.push(`**Series:** ${audio.series}`);
454
+ }
455
+ // Image details
456
+ if (content.image) {
457
+ const image = content.image;
458
+ if (image.description)
459
+ results.push(`**Description:** ${image.description}`);
460
+ if (image.software)
461
+ results.push(`**Software:** ${image.software}`);
462
+ if (image.make)
463
+ results.push(`**Make:** ${image.make}`);
464
+ if (image.model)
465
+ results.push(`**Model:** ${image.model}`);
466
+ }
467
+ // Collections
468
+ if (content.collections) {
469
+ results.push(...content.collections
470
+ .filter((collection) => collection !== null)
471
+ .slice(0, 100)
472
+ .map((collection) => `**Collection [${collection.name}]:** collections://${collection.id}`));
473
+ }
474
+ // Parent Content
475
+ if (content.parent) {
476
+ results.push(`**Parent Content:** contents://${content.parent.id}`);
477
+ }
478
+ // Child Content(s)
479
+ if (content.children) {
480
+ results.push(...content.children
481
+ .filter((child) => child !== null)
482
+ .slice(0, 100)
483
+ .map((child) => `**Child Content:** contents://${child.id}`));
484
+ }
485
+ // Links
486
+ if (content.links && content.type === ContentTypes.Page) {
487
+ results.push(...content.links
488
+ .slice(0, 1000)
489
+ .map((link) => `**${link.linkType} Link:** ${link.uri}`));
490
+ }
491
+ // Observations
492
+ if (content.observations) {
493
+ results.push(...content.observations
494
+ .filter((observation) => observation !== null)
495
+ .filter((observation) => observation.observable !== null)
496
+ .slice(0, 100)
497
+ .map((observation) => `**${observation.type}:** ${observation.type.toLowerCase()}s://${observation.observable.id}`));
498
+ }
499
+ // Content
500
+ if (content.pages?.length) {
501
+ content.pages.forEach((page) => {
502
+ if (page.chunks?.length) {
503
+ results.push(`**Page #${(page.index || 0) + 1}:**`);
504
+ results.push(...(page.chunks
505
+ ?.filter((chunk) => chunk?.text)
506
+ .map((chunk) => chunk?.text || "") || []));
507
+ results.push("\n---\n");
508
+ }
509
+ });
510
+ }
511
+ if (content.segments?.length) {
512
+ content.segments.forEach((segment) => {
513
+ results.push(`**Transcript Segment [${segment.startTime}-${segment.endTime}]:**`);
514
+ results.push(segment.text || "");
515
+ results.push("\n---\n");
516
+ });
517
+ }
518
+ if (content.frames?.length) {
519
+ content.frames.forEach((frame) => {
520
+ results.push(`**Frame #${(frame.index || 0) + 1}:**`);
521
+ results.push(frame.text || "");
522
+ results.push("\n---\n");
523
+ });
524
+ }
525
+ if (!content.pages?.length &&
526
+ !content.segments?.length &&
527
+ !content.frames?.length &&
528
+ content.markdown) {
529
+ results.push(content.markdown);
530
+ results.push("\n");
531
+ }
532
+ return results.join("\n");
533
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerTools(server: McpServer): void;