UNPKG

graphlit-mcp-server

Version:
1,107 lines (1,093 loc) 109 kB
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, SharePointAuthenticationTypes, FileTypes, TextTypes, SearchTypes, ContentPublishingServiceTypes, ContentPublishingFormats, ElevenLabsModels, IntegrationServiceTypes, TwitterListingTypes } from "graphlit-client/dist/generated/graphql-types.js"; export function registerTools(server) { server.tool("configureProject", `Configures the default content workflow for the Graphlit project. Only needed if user asks to configure the default workflow. 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.`, { enablePreparation: z.boolean().optional().default(false).describe("Whether to enable high-quality document and web page preparation using vision LLM. Defaults to False."), enableExtraction: z.boolean().optional().default(false).describe("Whether to enable entity extraction using LLM into the knowledge graph. Defaults to False."), serviceType: z.nativeEnum(Types.ModelServiceTypes).optional().default(Types.ModelServiceTypes.Anthropic).describe("Preferred model provider service type, i.e. Anthropic, OpenAI, Google. Defaults to Anthropic if not provided.") }, async ({ enablePreparation, enableExtraction, serviceType }) => { const client = new Graphlit(); var preparationSpecificationId; var extractionSpecificationId; var workflowId; switch (serviceType) { case Types.ModelServiceTypes.Anthropic: case Types.ModelServiceTypes.Google: case Types.ModelServiceTypes.OpenAi: break; default: throw new Error(`Unsupported model service type [${serviceType}].`); } if (enablePreparation) { var sresponse = await client.upsertSpecification({ name: "MCP Default Specification: Preparation", type: Types.SpecificationTypes.Preparation, serviceType: serviceType, anthropic: serviceType == Types.ModelServiceTypes.Anthropic ? { model: Types.AnthropicModels.Claude_3_7Sonnet } : undefined, openAI: serviceType == Types.ModelServiceTypes.OpenAi ? { model: Types.OpenAiModels.Gpt4O_128K } : undefined, google: serviceType == Types.ModelServiceTypes.Google ? { model: Types.GoogleModels.Gemini_2_5ProExperimental } : undefined, }); preparationSpecificationId = sresponse.upsertSpecification?.id; } if (enableExtraction) { var sresponse = await client.upsertSpecification({ name: "MCP Default Specification: Extraction", type: Types.SpecificationTypes.Extraction, serviceType: serviceType, anthropic: serviceType == Types.ModelServiceTypes.Anthropic ? { model: Types.AnthropicModels.Claude_3_7Sonnet } : undefined, openAI: serviceType == Types.ModelServiceTypes.OpenAi ? { model: Types.OpenAiModels.Gpt4O_128K } : undefined, google: serviceType == Types.ModelServiceTypes.Google ? { model: Types.GoogleModels.Gemini_2_5ProExperimental } : 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({ 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 the Graphlit API or SDKs. Can create code samples for any API call. Accepts an LLM user prompt for code generation. Returns the LLM prompt completion in Markdown format.`, { prompt: z.string().describe("LLM user prompt for code generation.") }, 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("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. 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."), 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."), 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, contentType, 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: contentType ? [contentType] : 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("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("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 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."), 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, type, fileType, inLast, feeds, collections, location, limit }) => { const client = new Graphlit(); try { const filter = { name: name, 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("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. 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. 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. 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(); try { const token = process.env.NOTION_API_KEY; if (!token) { console.error("Please set NOTION_API_KEY environment variable."); process.exit(1); } const response = await client.queryNotionDatabases({ token: token }); return { content: [{ type: "text", text: JSON.stringify(response.notionDatabases?.results, null, 2) }] }; } catch (err) { const error = err; return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } }); server.tool("listLinearProjects", `Lists available Linear projects. Returns a list of Linear projects, where the project name can be used with ingestLinearIssues to ingest issues into Graphlit knowledge base.`, {}, async ({}) => { const client = new Graphlit(); try { const apiKey = process.env.LINEAR_API_KEY; if (!apiKey) { console.error("Please set LINEAR_API_KEY environment variable."); process.exit(1); } const response = await client.queryLinearProjects({ key: apiKey }); return { content: [{ type: "text", text: JSON.stringify(response.linearProjects?.results, null, 2) }] }; } catch (err) { const error = err; return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } }); server.tool("listSlackChannels", `Lists available Slack channels. Returns a list of Slack channels, where the channel name can be used with ingestSlackMessages to ingest messages into Graphlit knowledge base.`, {}, async ({}) => { const client = new Graphlit(); try { const botToken = process.env.SLACK_BOT_TOKEN; if (!botToken) { console.error("Please set SLACK_BOT_TOKEN environment variable."); process.exit(1); } const response = await client.querySlackChannels({ token: botToken }); return { content: [{ type: "text", text: JSON.stringify(response.slackChannels?.results, null, 2) }] }; } catch (err) { const error = err; return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } }); server.tool("listSharePointLibraries", `Lists available SharePoint libraries. Returns a list of SharePoint libraries, where the selected libraryId can be used with listSharePointFolders to enumerate SharePoint folders in a library.`, {}, async ({}) => { const client = new Graphlit(); try { const clientId = process.env.SHAREPOINT_CLIENT_ID; if (!clientId) { console.error("Please set SHAREPOINT_CLIENT_ID environment variable."); process.exit(1); } const clientSecret = process.env.SHAREPOINT_CLIENT_SECRET; if (!clientSecret) { console.error("Please set SHAREPOINT_CLIENT_SECRET environment variable."); process.exit(1); } const refreshToken = process.env.SHAREPOINT_REFRESH_TOKEN; if (!refreshToken) { console.error("Please set SHAREPOINT_REFRESH_TOKEN environment variable."); process.exit(1); } const response = await client.querySharePointLibraries({ authenticationType: SharePointAuthenticationTypes.User, clientId: clientId, clientSecret: clientSecret, refreshToken: refreshToken, }); return { content: [{ type: "text", text: JSON.stringify(response.sharePointLibraries?.results, null, 2) }] }; } catch (err) { const error = err; return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } }); server.tool("listSharePointFolders", `Lists available SharePoint folders. Returns a list of SharePoint folders, which can be used with ingestSharePointFiles to ingest files into Graphlit knowledge base.`, { libraryId: z.string().describe("SharePoint library identifier.") }, async ({ libraryId }) => { const client = new Graphlit(); try { const clientId = process.env.SHAREPOINT_CLIENT_ID; if (!clientId) { console.error("Please set SHAREPOINT_CLIENT_ID environment variable."); process.exit(1); } const clientSecret = process.env.SHAREPOINT_CLIENT_SECRET; if (!clientSecret) { console.error("Please set SHAREPOINT_CLIENT_SECRET environment variable."); process.exit(1); } const refreshToken = process.env.SHAREPOINT_REFRESH_TOKEN; if (!refreshToken) { console.error("Please set SHAREPOINT_REFRESH_TOKEN environment variable."); process.exit(1); } const response = await client.querySharePointFolders({ authenticationType: SharePointAuthenticationTypes.User, clientId: clientId, clientSecret: clientSecret, refreshToken: refreshToken, }, libraryId); return { content: [{ type: "text", text: JSON.stringify(response.sharePointFolders?.results, null, 2) }] }; } catch (err) { const error = err; return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true }; } }); server.tool("ingestSharePointFiles", `Ingests files from SharePoint library into Graphlit knowledge base. Accepts a SharePoint libraryId and an optional folderId to ingest files from a specific SharePoint folder. Libraries can be enumerated with listSharePointLibraries and library folders with listSharePointFolders. Accepts an optional read limit for the number of files to ingest. Executes asynchronously and returns the feed identifier.`, { libraryId: z.string().describe("SharePoint library identifier."), folderId: z.string().optional().describe("SharePoint folder identifier, optional."), readLimit: z.number().optional().describe("Number of files to ingest, optional. Defaults to 100.") }, async ({ libraryId, folderId, readLimit }) => { const client = new Graphlit(); try { const accountName = process.env.SHAREPOINT_ACCOUNT_NAME; if (!accountName) { console.error("Please set SHAREPOINT_ACCOUNT_NAME environment variable."); process.exit(1); } const clientId = process.env.SHAREPOINT_CLIENT_ID; if (!clientId) { console.error("Please set SHAREPOINT_CLIENT_ID environment variable."); process.exit(1); } const clientSecret = process.env.SHAREPOINT_CLIENT_SECRET; if (!clientSecret) { console.error("Please set SHAREPOINT_CLIENT_SECRET environment variable."); process.exit(1); } const refreshToken = process.env.SHAREPOINT_REFRESH_TOKEN; if (!refreshToken) { console.error("Please set SHAREPOINT_REFRESH_TOKEN environment variable."); process.exit(1); } const response = await client.createFeed({ name: `SharePoint`, type: FeedTypes.Site, site: { type: FeedServiceTypes.SharePoint, sharePoint: { authenticationType: SharePointAuthenticationTypes.User, accountName: accountName, clientId: clientId, clientSecret: clientSecret, refreshToken: refreshToken, libraryId: libraryId, folderId: folderId }, isRecursive: true, readLimit: readLimit || 100 } }); return { content: [{ type: "text", text: JSON.stringify({ id: response.createFeed?.id }, null, 2) }]