@mseep/graphlit-mcp-server
Version:
Graphlit MCP Server
1,058 lines (1,044 loc) • 125 kB
JavaScript
import fs from 'fs';
import path from 'path';
import mime from 'mime-types';
import { Graphlit, Types } from "graphlit-client";
import { z } from "zod";
import { ContentTypes, FeedServiceTypes, EmailListingTypes, SearchServiceTypes, FeedListingTypes, FeedTypes, NotionTypes, RerankingModelServiceTypes, RetrievalStrategyTypes, GoogleDriveAuthenticationTypes, SharePointAuthenticationTypes, FileTypes, TextTypes, SearchTypes, ContentPublishingServiceTypes, ContentPublishingFormats, ElevenLabsModels, IntegrationServiceTypes, TwitterListingTypes, ConversationSearchTypes, PromptStrategyTypes } from "graphlit-client/dist/generated/graphql-types.js";
export function registerTools(server) {
server.tool("configureProject", `Configures the default content workflow and conversation specification for the Graphlit project.
Only needed if user asks to configure the project defaults. *Do not* call unless specifically asked for by the user.
To reset the project configuration to 'factory state', assign False or null to all parameters.
Optionally accepts whether to configure the default specification for LLM conversations. Defaults to using OpenAI GPT-4o, if not assigned.
Optionally accepts whether to enable high-quality document and web page preparation using a vision LLM. Defaults to using Azure AI Document Intelligence for document preparation, if not assigned.
Optionally accepts whether to enable entity extraction using LLM into the knowledge graph. Defaults to no entity extraction, if not assigned.
Optionally accepts the preferred model provider service type, i.e. Anthropic, OpenAI, Google. Defaults to Anthropic if not provided.
Returns the project identifier.`, {
modelServiceType: z.nativeEnum(Types.ModelServiceTypes).optional().default(Types.ModelServiceTypes.Anthropic).describe("Preferred model provider service type for all specifications, i.e. Anthropic, OpenAI, Google. Defaults to Anthropic if not provided."),
configureConversationSpecification: z.boolean().optional().default(false).describe("Whether to configure the default specification for LLM conversations. Defaults to False."),
configurePreparationSpecification: z.boolean().optional().default(false).describe("Whether to configure high-quality document and web page preparation using vision LLM. Defaults to False."),
configureExtractionSpecification: z.boolean().optional().default(false).describe("Whether to configure entity extraction using LLM into the knowledge graph. Defaults to False."),
}, async ({ modelServiceType, configureConversationSpecification, configurePreparationSpecification, configureExtractionSpecification }) => {
const client = new Graphlit();
var preparationSpecificationId;
var extractionSpecificationId;
var completionSpecificationId;
var workflowId;
switch (modelServiceType) {
case Types.ModelServiceTypes.Anthropic:
case Types.ModelServiceTypes.Google:
case Types.ModelServiceTypes.OpenAi:
break;
default:
throw new Error(`Unsupported model service type [${modelServiceType}].`);
}
if (configureConversationSpecification) {
var sresponse = await client.upsertSpecification({
name: "MCP Default Specification: Completion",
type: Types.SpecificationTypes.Completion,
serviceType: modelServiceType,
anthropic: modelServiceType == Types.ModelServiceTypes.Anthropic ? {
model: Types.AnthropicModels.Claude_3_7Sonnet
} : undefined,
openAI: modelServiceType == Types.ModelServiceTypes.OpenAi ? {
model: Types.OpenAiModels.Gpt4OChat_128K
} : undefined,
google: modelServiceType == Types.ModelServiceTypes.Google ? {
model: Types.GoogleModels.Gemini_2_0Flash
} : undefined,
searchType: ConversationSearchTypes.Hybrid,
strategy: {
embedCitations: true,
},
promptStrategy: {
type: PromptStrategyTypes.OptimizeSearch, // optimize for similarity search
},
retrievalStrategy: {
type: RetrievalStrategyTypes.Section // expand chunk to section
},
rerankingStrategy: {
serviceType: RerankingModelServiceTypes.Cohere,
}
});
completionSpecificationId = sresponse.upsertSpecification?.id;
}
if (configurePreparationSpecification) {
var sresponse = await client.upsertSpecification({
name: "MCP Default Specification: Preparation",
type: Types.SpecificationTypes.Preparation,
serviceType: modelServiceType,
anthropic: modelServiceType == Types.ModelServiceTypes.Anthropic ? {
model: Types.AnthropicModels.Claude_3_7Sonnet,
enableThinking: true,
} : undefined,
openAI: modelServiceType == Types.ModelServiceTypes.OpenAi ? {
model: Types.OpenAiModels.Gpt4O_128K
} : undefined,
google: modelServiceType == Types.ModelServiceTypes.Google ? {
model: Types.GoogleModels.Gemini_2_5ProPreview
} : undefined,
});
preparationSpecificationId = sresponse.upsertSpecification?.id;
}
if (configureExtractionSpecification) {
var sresponse = await client.upsertSpecification({
name: "MCP Default Specification: Extraction",
type: Types.SpecificationTypes.Extraction,
serviceType: modelServiceType,
anthropic: modelServiceType == Types.ModelServiceTypes.Anthropic ? {
model: Types.AnthropicModels.Claude_3_7Sonnet
} : undefined,
openAI: modelServiceType == Types.ModelServiceTypes.OpenAi ? {
model: Types.OpenAiModels.Gpt4O_128K
} : undefined,
google: modelServiceType == Types.ModelServiceTypes.Google ? {
model: Types.GoogleModels.Gemini_2_0Flash
} : undefined,
});
extractionSpecificationId = sresponse.upsertSpecification?.id;
}
const wresponse = await client.upsertWorkflow({
name: "MCP Default Workflow",
preparation: preparationSpecificationId !== undefined ? {
jobs: [{
connector: {
type: Types.FilePreparationServiceTypes.ModelDocument,
modelDocument: {
specification: { id: preparationSpecificationId }
}
}
}]
} : undefined,
extraction: extractionSpecificationId !== undefined ? {
jobs: [{
connector: {
type: Types.EntityExtractionServiceTypes.ModelText,
modelText: {
specification: { id: extractionSpecificationId }
}
}
}, {
connector: {
type: Types.EntityExtractionServiceTypes.ModelImage,
modelImage: {
specification: { id: extractionSpecificationId }
}
}
}]
} : undefined
});
workflowId = wresponse.upsertWorkflow?.id;
try {
const response = await client.updateProject({
specification: completionSpecificationId !== undefined ? { id: completionSpecificationId } : undefined,
workflow: workflowId !== undefined ? { id: workflowId } : undefined
});
return {
content: [{
type: "text",
text: JSON.stringify({ id: response.updateProject?.id }, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("askGraphlit", `Ask questions about using the Graphlit Platform, or specifically about the Graphlit API or SDKs.
When the user asks about how to use the Graphlit API or SDKs, use this tool to provide a code sample in Python, TypeScript or C#.
Accepts an LLM user prompt.
Returns the LLM prompt completion in Markdown format.`, {
prompt: z.string().describe("LLM user prompt.")
}, async ({ prompt }) => {
const client = new Graphlit();
try {
const response = await client.askGraphlit(prompt);
const message = response.askGraphlit?.message;
return {
content: [{
type: "text",
text: JSON.stringify(message, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("promptConversation", `Prompts an LLM conversation about your entire Graphlit knowledge base.
Uses hybrid vector search based on user prompt for locating relevant content sources. Uses LLM to complete the user prompt with the configured LLM.
Maintains conversation history between 'user' and LLM 'assistant'.
Prefer 'promptConversation' when the user intends to start or continue an ongoing conversation about the entire Graphlit knowledge base.
Similar to 'retrieveSources' but does not perform content metadata filtering.
Accepts an LLM user prompt and optional conversation identifier. Will either create a new conversation or continue an existing one.
Will use the default specification for LLM conversations, which is optionally configured with the 'configureProject' tool.
Returns the conversation identifier, completed LLM message, and any citations from the LLM response.`, {
prompt: z.string().describe("User prompt."),
conversationId: z.string().optional().describe("Conversation identifier, optional."),
}, async ({ prompt, conversationId }) => {
const client = new Graphlit();
try {
const response = await client.promptConversation(prompt, conversationId);
return {
content: [{
type: "text",
text: JSON.stringify({
id: response.promptConversation?.conversation?.id,
message: response.promptConversation?.message?.message,
citations: response.promptConversation?.message?.citations,
}, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("retrieveSources", `Retrieve relevant content sources from Graphlit knowledge base. Do *not* use for retrieving content by content identifier - retrieve content resource instead, with URI 'contents://{id}'.
Accepts an LLM user prompt for content retrieval. For best retrieval quality, provide only key words or phrases from the user prompt, which will be used to create text embeddings for a vector search query.
Only use when there is a valid LLM user prompt for content retrieval, otherwise use 'queryContents'. For example 'recent content' is not a useful user prompt, since it doesn't reference the text in the content.
Only use for 'one shot' retrieval of content sources, i.e. when the user is not interested in having a conversation about the content.
Accepts an optional ingestion recency filter (defaults to null, meaning all time), and optional content type and file type filters.
Also accepts optional feed and collection identifiers to filter content by.
Returns the ranked content sources, including their content resource URI to retrieve the complete Markdown text.`, {
prompt: z.string().describe("LLM user prompt for content retrieval."),
inLast: z.string().optional().describe("Recency filter for content ingested 'in last' timespan, optional. Should be ISO 8601 format, for example, 'PT1H' for last hour, 'P1D' for last day, 'P7D' for last week, 'P30D' for last month. Doesn't support weeks or months explicitly."),
type: z.nativeEnum(ContentTypes).optional().describe("Content type filter, optional. One of: Email, Event, File, Issue, Message, Page, Post, Text."),
fileType: z.nativeEnum(FileTypes).optional().describe("File type filter, optional. One of: Animation, Audio, Code, Data, Document, Drawing, Email, Geometry, Image, Package, PointCloud, Shape, Video."),
feeds: z.array(z.string()).optional().describe("Feed identifiers to filter content by, optional."),
collections: z.array(z.string()).optional().describe("Collection identifiers to filter content by, optional.")
}, async ({ prompt, type, fileType, inLast, feeds, collections }) => {
const client = new Graphlit();
try {
const filter = {
searchType: SearchTypes.Hybrid,
feeds: feeds?.map(feed => ({ id: feed })),
collections: collections?.map(collection => ({ id: collection })),
createdInLast: inLast,
types: type ? [type] : null,
fileTypes: fileType ? [fileType] : null
};
const response = await client.retrieveSources(prompt, filter, undefined, {
type: RetrievalStrategyTypes.Chunk,
disableFallback: true
}, {
serviceType: RerankingModelServiceTypes.Cohere
});
const sources = response.retrieveSources?.results || [];
return {
content: sources
.filter(source => source !== null)
.map(source => ({
type: "text",
mimeType: "application/json",
text: JSON.stringify({
id: source.content?.id,
relevance: source.relevance,
resourceUri: `contents://${source.content?.id}`,
text: source.text,
mimeType: "text/markdown"
}, null, 2)
}))
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
const PointFilter = z.object({
latitude: z.number().min(-90).max(90)
.describe("The latitude, must be between -90 and 90."),
longitude: z.number().min(-180).max(180)
.describe("The longitude, must be between -180 and 180."),
distance: z.number().optional()
.describe("The distance radius (in meters)."),
});
//
// REVIEW: MCP clients don't handle Base64-encoded data very well,
// will often exceed the LLM context window to return from the tool
// so, we only can support similar images by URL
server.tool("retrieveImages", `Retrieve images from Graphlit knowledge base. Provides image-specific retrieval when image similarity search is desired.
Do *not* use for retrieving content by content identifier - retrieve content resource instead, with URI 'contents://{id}'.
Accepts image URL. Image will be used for similarity search using image embeddings.
Accepts optional geo-location filter for search by latitude, longitude and optional distance radius. Images taken with GPS enabled are searchable by geo-location.
Also accepts optional recency filter (defaults to null, meaning all time), and optional feed and collection identifiers to filter images by.
Returns the matching images, including their content resource URI to retrieve the complete Markdown text.`, {
url: z.string().describe("URL of image which will be used for similarity search using image embeddings."),
inLast: z.string().optional().describe("Recency filter for images ingested 'in last' timespan, optional. Should be ISO 8601 format, for example, 'PT1H' for last hour, 'P1D' for last day, 'P7D' for last week, 'P30D' for last month. Doesn't support weeks or months explicitly."),
feeds: z.array(z.string()).optional().describe("Feed identifiers to filter images by, optional."),
collections: z.array(z.string()).optional().describe("Collection identifiers to filter images by, optional."),
location: PointFilter.optional().describe("Geo-location filter for search by latitude, longitude and optional distance radius."),
limit: z.number().optional().default(100).describe("Limit the number of images to be returned. Defaults to 100.")
}, async ({ url, inLast, feeds, collections, location, limit }) => {
const client = new Graphlit();
var data;
var mimeType;
if (url) {
const fetchResponse = await fetch(url);
if (!fetchResponse.ok) {
throw new Error(`Failed to fetch data from ${url}: ${fetchResponse.statusText}`);
}
const arrayBuffer = await fetchResponse.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
data = buffer.toString('base64');
mimeType = fetchResponse.headers.get('content-type') || 'application/octet-stream';
}
try {
const filter = {
imageData: data,
imageMimeType: mimeType,
searchType: SearchTypes.Vector,
feeds: feeds?.map(feed => ({ id: feed })),
collections: collections?.map(collection => ({ id: collection })),
location: location,
createdInLast: inLast,
types: [ContentTypes.File],
fileTypes: [FileTypes.Image],
limit: limit
};
const response = await client.queryContents(filter);
const contents = response.contents?.results || [];
return {
content: contents
.filter(content => content !== null)
.map(content => ({
type: "text",
mimeType: "application/json",
text: JSON.stringify({
id: content.id,
relevance: content.relevance,
fileName: content.fileName,
resourceUri: `contents://${content.id}`,
uri: content.imageUri,
mimeType: content.mimeType
}, null, 2)
}))
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("extractText", `Extracts JSON data from text using LLM.
Accepts text to be extracted, and JSON schema which describes the data which will be extracted. JSON schema needs be of type 'object' and include 'properties' and 'required' fields.
Optionally accepts text prompt which is provided to LLM to guide data extraction. Defaults to 'Extract data using the tools provided'.
Returns extracted JSON from text.`, {
text: z.string().describe("Text to be extracted with LLM."),
schema: z.string().describe("JSON schema which describes the data which will be extracted. JSON schema needs be of type 'object' and include 'properties' and 'required' fields."),
prompt: z.string().optional().describe("Text prompt which is provided to LLM to guide data extraction, optional.")
}, async ({ text, schema, prompt }) => {
const client = new Graphlit();
const DEFAULT_NAME = "extract_json";
const DEFAULT_PROMPT = `
Extract data using the tools provided.
`;
try {
const response = await client.extractText(prompt || DEFAULT_PROMPT, text, [{ name: DEFAULT_NAME, schema: schema }]);
return {
content: [{
type: "text",
text: JSON.stringify(response.extractText ? response.extractText.filter(item => item !== null).map(item => item.value) : [], null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("createCollection", `Create a collection.
Accepts a collection name, and optional list of content identifiers to add to collection.
Returns the collection identifier`, {
name: z.string().describe("Collection name."),
contents: z.array(z.string()).optional().describe("Content identifiers to add to collection, optional.")
}, async ({ name, contents }) => {
const client = new Graphlit();
try {
const response = await client.createCollection({
name: name,
contents: contents?.map(content => ({ id: content })),
});
return {
content: [{
type: "text",
text: JSON.stringify({ id: response.createCollection?.id }, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("addContentsToCollection", `Add contents to a collection.
Accepts a collection identifier and a list of content identifiers to add to collection.
Returns the collection identifier.`, {
id: z.string().describe("Collection identifier."),
contents: z.array(z.string()).describe("Content identifiers to add to collection.")
}, async ({ id, contents }) => {
const client = new Graphlit();
try {
const response = await client.addContentsToCollections(contents?.map(content => ({ id: content })), [{ id: id }]);
return {
content: [{
type: "text",
text: JSON.stringify({ id: id }, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("removeContentsFromCollection", `Remove contents from collection.
Accepts a collection identifier and a list of content identifiers to remove from collection.
Returns the collection identifier.`, {
id: z.string().describe("Collection identifier."),
contents: z.array(z.string()).describe("Content identifiers to remove from collection.")
}, async ({ id, contents }) => {
const client = new Graphlit();
try {
const response = await client.removeContentsFromCollection(contents?.map(content => ({ id: content })), { id: id });
return {
content: [{
type: "text",
text: JSON.stringify({ id: response.removeContentsFromCollection?.id }, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteContent", `Deletes content from Graphlit knowledge base.
Accepts content identifier.
Returns the content identifier and content state, i.e. Deleted.`, {
id: z.string().describe("Content identifier."),
}, async ({ id }) => {
const client = new Graphlit();
try {
const response = await client.deleteContent(id);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteContent, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteConversation", `Deletes conversation from Graphlit knowledge base.
Accepts conversation identifier.
Returns the conversation identifier and content state, i.e. Deleted.`, {
id: z.string().describe("Conversation identifier."),
}, async ({ id }) => {
const client = new Graphlit();
try {
const response = await client.deleteConversation(id);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteConversation, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteCollection", `Deletes collection from Graphlit knowledge base.
Does *not* delete the contents in the collection, only the collection itself.
Accepts collection identifier.
Returns the collection identifier and collection state, i.e. Deleted.`, {
id: z.string().describe("Collection identifier."),
}, async ({ id }) => {
const client = new Graphlit();
try {
const response = await client.deleteCollection(id);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteCollection, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteFeed", `Deletes feed from Graphlit knowledge base.
*Does* delete the contents in the feed, in addition to the feed itself.
Accepts feed identifier.
Returns the feed identifier and feed state, i.e. Deleted.`, {
id: z.string().describe("Feed identifier."),
}, async ({ id }) => {
const client = new Graphlit();
try {
const response = await client.deleteFeed(id);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteFeed, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteFeeds", `Deletes feeds from Graphlit knowledge base.
*Does* delete the contents in the feed, in addition to the feed itself.
Accepts optional feed type filter to limit the feeds which will be deleted.
Also accepts optional limit of how many feeds to delete, defaults to 100.
Returns the feed identifiers and feed state, i.e. Deleted.`, {
feedType: z.nativeEnum(FeedTypes).optional().describe("Feed type filter, optional. One of: Discord, Email, Intercom, Issue, MicrosoftTeams, Notion, Reddit, Rss, Search, Site, Slack, Web, YouTube, Zendesk."),
limit: z.number().optional().default(100).describe("Limit the number of feeds to be deleted. Defaults to 100.")
}, async ({ feedType, limit }) => {
const client = new Graphlit();
try {
const filter = {
types: feedType ? [feedType] : null,
limit: limit
};
const response = await client.deleteAllFeeds(filter, true);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteAllFeeds, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteCollections", `Deletes collections from Graphlit knowledge base.
Does *not* delete the contents in the collections, only the collections themselves.
Accepts optional limit of how many collections to delete, defaults to 100.
Returns the collection identifiers and collection state, i.e. Deleted.`, {
limit: z.number().optional().default(100).describe("Limit the number of collections to be deleted. Defaults to 100.")
}, async ({ limit }) => {
const client = new Graphlit();
try {
const filter = {
limit: limit
};
const response = await client.deleteAllCollections(filter, true);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteAllCollections, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteConversations", `Deletes conversations from Graphlit knowledge base.
Accepts optional limit of how many conversations to delete, defaults to 100.
Returns the conversation identifiers and conversation state, i.e. Deleted.`, {
limit: z.number().optional().default(100).describe("Limit the number of conversations to be deleted. Defaults to 100.")
}, async ({ limit }) => {
const client = new Graphlit();
try {
const filter = {
limit: limit
};
const response = await client.deleteAllConversations(filter, true);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteAllConversations, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("deleteContents", `Deletes contents from Graphlit knowledge base.
Accepts optional content type and file type filters to limit the contents which will be deleted.
Also accepts optional limit of how many contents to delete, defaults to 1000.
Returns the content identifiers and content state, i.e. Deleted.`, {
contentType: z.nativeEnum(ContentTypes).optional().describe("Content type filter, optional. One of: Email, Event, File, Issue, Message, Page, Post, Text."),
fileType: z.nativeEnum(FileTypes).optional().describe("File type filter, optional. One of: Animation, Audio, Code, Data, Document, Drawing, Email, Geometry, Image, Package, PointCloud, Shape, Video."),
limit: z.number().optional().default(1000).describe("Limit the number of contents to be deleted. Defaults to 1000.")
}, async ({ contentType, fileType, limit }) => {
const client = new Graphlit();
try {
const filter = {
types: contentType ? [contentType] : null,
fileTypes: fileType ? [fileType] : null,
limit: limit
};
const response = await client.deleteAllContents(filter, true);
return {
content: [{
type: "text",
text: JSON.stringify(response.deleteAllContents, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("queryContents", `Query contents from Graphlit knowledge base. Do *not* use for retrieving content by content identifier - retrieve content resource instead, with URI 'contents://{id}'.
Accepts optional content name, content type and file type for metadata filtering.
Accepts optional hybrid vector search query.
Accepts optional recency filter (defaults to null, meaning all time), and optional feed and collection identifiers to filter images by.
Accepts optional geo-location filter for search by latitude, longitude and optional distance radius. Images and videos taken with GPS enabled are searchable by geo-location.
Returns the matching contents, including their content resource URI to retrieve the complete Markdown text.`, {
name: z.string().optional().describe("Textual match on content name."),
query: z.string().optional().describe("Search query."),
type: z.nativeEnum(ContentTypes).optional().describe("Filter by content type."),
fileType: z.nativeEnum(FileTypes).optional().describe("Filter by file type."),
inLast: z.string().optional().describe("Recency filter for content ingested 'in last' timespan, optional. Should be ISO 8601 format, for example, 'PT1H' for last hour, 'P1D' for last day, 'P7D' for last week, 'P30D' for last month. Doesn't support weeks or months explicitly."),
feeds: z.array(z.string()).optional().describe("Feed identifiers to filter contents by, optional."),
collections: z.array(z.string()).optional().describe("Collection identifiers to filter contents by, optional."),
location: PointFilter.optional().describe("Geo-location filter for search by latitude, longitude and optional distance radius."),
limit: z.number().optional().default(100).describe("Limit the number of contents to be returned. Defaults to 100.")
}, async ({ name, query, type, fileType, inLast, feeds, collections, location, limit }) => {
const client = new Graphlit();
try {
const filter = {
name: name,
search: query,
searchType: SearchTypes.Hybrid,
types: type !== undefined ? [type] : undefined,
fileTypes: fileType !== undefined ? [fileType] : undefined,
feeds: feeds?.map(feed => ({ id: feed })),
collections: collections?.map(collection => ({ id: collection })),
location: location,
createdInLast: inLast,
limit: limit
};
const response = await client.queryContents(filter);
const contents = response.contents?.results || [];
return {
content: contents
.filter(content => content !== null)
.map(content => ({
type: "text",
mimeType: "application/json",
text: JSON.stringify({
id: content.id,
relevance: content.relevance,
fileName: content.fileName,
resourceUri: `contents://${content.id}`,
uri: content.imageUri,
mimeType: content.mimeType
}, null, 2)
}))
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("queryCollections", `Query collections from Graphlit knowledge base. Do *not* use for retrieving collection by collection identifier - retrieve collection resource instead, with URI 'collections://{id}'.
Accepts optional collection name for metadata filtering.
Returns the matching collections, including their collection resource URI to retrieve the collection contents.`, {
name: z.string().optional().describe("Textual match on collection name."),
limit: z.number().optional().default(100).describe("Limit the number of collections to be returned. Defaults to 100.")
}, async ({ name, limit }) => {
const client = new Graphlit();
try {
const filter = {
name: name,
limit: limit
};
const response = await client.queryCollections(filter);
const collections = response.collections?.results || [];
return {
content: collections
.filter(collection => collection !== null)
.map(collection => ({
type: "text",
mimeType: "application/json",
text: JSON.stringify({
id: collection.id,
relevance: collection.relevance,
resourceUri: `collections://${collection.id}`
}, null, 2)
}))
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("queryFeeds", `Query feeds from Graphlit knowledge base. Do *not* use for retrieving feed by feed identifier - retrieve feed resource instead, with URI 'feeds://{id}'.
Accepts optional feed name and feed type for metadata filtering.
Returns the matching feeds, including their feed resource URI to retrieve the feed contents.`, {
name: z.string().optional().describe("Textual match on feed name."),
type: z.nativeEnum(FeedTypes).optional().describe("Filter by feed type."),
limit: z.number().optional().default(100).describe("Limit the number of feeds to be returned. Defaults to 100.")
}, async ({ name, type, limit }) => {
const client = new Graphlit();
try {
const filter = {
name: name,
types: type !== undefined ? [type] : undefined,
limit: limit
};
const response = await client.queryFeeds(filter);
const feeds = response.feeds?.results || [];
return {
content: feeds
.filter(feed => feed !== null)
.map(feed => ({
type: "text",
mimeType: "application/json",
text: JSON.stringify({
id: feed.id,
relevance: feed.relevance,
resourceUri: `feeds://${feed.id}`
}, null, 2)
}))
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("queryConversations", `Query conversations from Graphlit knowledge base. Do *not* use for retrieving conversation by conversation identifier - retrieve conversation resource instead, with URI 'conversations://{id}'.
Accepts optional hybrid vector search query.
Accepts optional recency filter (defaults to null, meaning all time).
Returns the matching conversations, including their conversation resource URI to retrieve the complete conversation message history.`, {
query: z.string().optional().describe("Search query."),
inLast: z.string().optional().describe("Recency filter for conversations created 'in last' timespan, optional. Should be ISO 8601 format, for example, 'PT1H' for last hour, 'P1D' for last day, 'P7D' for last week, 'P30D' for last month. Doesn't support weeks or months explicitly."),
limit: z.number().optional().default(100).describe("Limit the number of conversations to be returned. Defaults to 100.")
}, async ({ query, inLast, limit }) => {
const client = new Graphlit();
try {
const filter = {
search: query,
searchType: SearchTypes.Hybrid,
createdInLast: inLast,
limit: limit
};
const response = await client.queryConversations(filter);
const conversations = response.conversations?.results || [];
return {
content: conversations
.filter(conversation => conversation !== null)
.map(conversation => ({
type: "text",
mimeType: "application/json",
text: JSON.stringify({
id: conversation.id,
relevance: conversation.relevance,
resourceUri: `conversations://${conversation.id}`
}, null, 2)
}))
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("isContentDone", `Check if content has completed asynchronous ingestion.
Accepts a content identifier which was returned from one of the non-feed ingestion tools, like ingestUrl.
Returns whether the content is done or not.`, {
id: z.string().describe("Content identifier."),
}, async ({ id }) => {
const client = new Graphlit();
try {
const response = await client.isContentDone(id);
return {
content: [{
type: "text",
text: JSON.stringify({ done: response.isContentDone?.result }, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
server.tool("isFeedDone", `Check if an asynchronous feed has completed ingesting all the available content.
Accepts a feed identifier which was returned from one of the ingestion tools, like ingestGoogleDriveFiles.
Returns whether the feed is done or not.`, {
id: z.string().describe("Feed identifier."),
}, async ({ id }) => {
const client = new Graphlit();
try {
const response = await client.isFeedDone(id);
return {
content: [{
type: "text",
text: JSON.stringify({ done: response.isFeedDone?.result }, null, 2)
}]
};
}
catch (err) {
const error = err;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
});
/*
server.tool(
"listMicrosoftTeamsTeams",
`Lists available Microsoft Teams teams.
Requires environment variables to be configured: MICROSOFT_TEAMS_CLIENT_ID, MICROSOFT_TEAMS_CLIENT_SECRET, MICROSOFT_TEAMS_REFRESH_TOKEN.
Returns a list of Microsoft Teams teams, where the team identifier can be used with listMicrosoftTeamsChannels to enumerate Microsoft Teams channels.`,
{
},
async ({ }) => {
const client = new Graphlit();
try {
const clientId = process.env.MICROSOFT_TEAMS_CLIENT_ID;
if (!clientId) {
console.error("Please set MICROSOFT_TEAMS_CLIENT_ID environment variable.");
process.exit(1);
}
const clientSecret = process.env.MICROSOFT_TEAMS_CLIENT_SECRET;
if (!clientSecret) {
console.error("Please set MICROSOFT_TEAMS_CLIENT_SECRET environment variable.");
process.exit(1);
}
const refreshToken = process.env.MICROSOFT_TEAMS_REFRESH_TOKEN;
if (!refreshToken) {
console.error("Please set MICROSOFT_TEAMS_REFRESH_TOKEN environment variable.");
process.exit(1);
}
// REVIEW: client ID/secret not exposed in SDK
const response = await client.queryMicrosoftTeamsTeams({
//clientId: clientId,
//clientSecret: clientSecret,
refreshToken: refreshToken,
});
return {
content: [{
type: "text",
text: JSON.stringify(response.microsoftTeamsTeams?.results, null, 2)
}]
};
} catch (err: unknown) {
const error = err as Error;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
}
);
server.tool(
"listMicrosoftTeamsChannels",
`Lists available Microsoft Teams channels.
Requires environment variables to be configured: MICROSOFT_TEAMS_CLIENT_ID, MICROSOFT_TEAMS_CLIENT_SECRET, MICROSOFT_TEAMS_REFRESH_TOKEN.
Returns a list of Microsoft Teams channels, where the channel identifier can be used with ingestMicrosoftTeamsMessages to ingest messages into Graphlit knowledge base.`,
{
teamId: z.string().describe("Microsoft Teams team identifier.")
},
async ({ teamId }) => {
const client = new Graphlit();
try {
const clientId = process.env.MICROSOFT_TEAMS_CLIENT_ID;
if (!clientId) {
console.error("Please set MICROSOFT_TEAMS_CLIENT_ID environment variable.");
process.exit(1);
}
const clientSecret = process.env.MICROSOFT_TEAMS_CLIENT_SECRET;
if (!clientSecret) {
console.error("Please set MICROSOFT_TEAMS_CLIENT_SECRET environment variable.");
process.exit(1);
}
const refreshToken = process.env.MICROSOFT_TEAMS_REFRESH_TOKEN;
if (!refreshToken) {
console.error("Please set MICROSOFT_TEAMS_REFRESH_TOKEN environment variable.");
process.exit(1);
}
// REVIEW: client ID/secret not exposed in SDK
const response = await client.queryMicrosoftTeamsChannels({
//clientId: clientId,
//clientSecret: clientSecret,
refreshToken: refreshToken,
}, teamId);
return {
content: [{
type: "text",
text: JSON.stringify(response.microsoftTeamsChannels?.results, null, 2)
}]
};
} catch (err: unknown) {
const error = err as Error;
return {
content: [{
type: "text",
text: `Error: ${error.message}`
}],
isError: true
};
}
}
);
*/
server.tool("listNotionDatabases", `Lists available Notion databases.
Requires environment variable to be configured: NOTION_API_KEY.
Returns a list of Notion databases, where the database identifier can be used with ingestNotionPages to ingest pages into Graphlit knowledge base.`, {}, async ({}) => {
const client = new Graphlit();