├── glama.json ├── .prettierrc ├── .prettierignore ├── tsconfig.json ├── Dockerfile ├── azure-pipelines.yml ├── LICENSE ├── .env.example ├── package.json ├── .gitignore ├── smithery.yaml ├── src ├── index.ts └── resources.ts └── README.md /glama.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://glama.ai/mcp/schemas/server.json", 3 | "maintainers": ["kirk-marple"] 4 | } 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": false, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | 4 | # Build output 5 | build/ 6 | dist/ 7 | 8 | # Package files 9 | package-lock.json 10 | 11 | # Environment files 12 | .env 13 | .env.* 14 | 15 | # Logs 16 | *.log 17 | 18 | # IDE files 19 | .vscode/ 20 | .idea/ 21 | 22 | # OS generated files 23 | .DS_Store 24 | Thumbs.db 25 | 26 | .claude/ -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "esnext", // ESM output 5 | "moduleResolution": "node", 6 | "outDir": "dist", 7 | "declaration": true, 8 | "strict": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true, 13 | "lib": ["ES2022", "esnext.intl"] 14 | }, 15 | "include": ["src/**/*"] 16 | } 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile 2 | FROM node:lts-alpine 3 | 4 | # Create app directory 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies 8 | COPY package*.json ./ 9 | RUN npm install --production --ignore-scripts 10 | 11 | # Copy app source code 12 | COPY . . 13 | 14 | # Build the project 15 | RUN npm run build 16 | 17 | # Expose port if needed (adjust if your server listens on a port) 18 | # EXPOSE 3000 19 | 20 | # Start the server 21 | CMD ["node", "build/index.js"] 22 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - main 5 | 6 | name: $(Date:yyyyMMdd)$(Rev:rrr) 7 | 8 | pool: 9 | vmImage: "ubuntu-latest" 10 | 11 | steps: 12 | - task: NodeTool@0 13 | inputs: 14 | versionSpec: "20.x" 15 | displayName: "Install Node.js" 16 | 17 | - script: | 18 | npm install 19 | npm run build 20 | npm version 1.0.$(Build.BuildNumber) --no-git-tag-version --allow-same-version 21 | displayName: "Install dependencies and build" 22 | 23 | - task: CopyFiles@2 24 | inputs: 25 | SourceFolder: "$(Build.SourcesDirectory)" 26 | Contents: "README.md" 27 | TargetFolder: "$(Build.ArtifactStagingDirectory)/build" 28 | 29 | - task: CopyFiles@2 30 | inputs: 31 | SourceFolder: "$(Build.SourcesDirectory)" 32 | Contents: "LICENSE" 33 | TargetFolder: "$(Build.ArtifactStagingDirectory)/build" 34 | 35 | - task: Npm@1 36 | inputs: 37 | command: publish 38 | publishRegistry: useExternalRegistry 39 | publishEndpoint: "NPM" 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Unstruk Data Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | GRAPHLIT_ORGANIZATION_ID= 2 | GRAPHLIT_ENVIRONMENT_ID= 3 | GRAPHLIT_JWT_SECRET= 4 | 5 | SLACK_BOT_TOKEN= 6 | 7 | DISCORD_BOT_TOKEN= 8 | 9 | TWITTER_TOKEN= 10 | 11 | MICROSOFT_TEAMS_CLIENT_ID= 12 | MICROSOFT_TEAMS_CLIENT_SECRET= 13 | MICROSOFT_TEAMS_REFRESH_TOKEN= 14 | 15 | GOOGLE_EMAIL_REFRESH_TOKEN= 16 | GOOGLE_EMAIL_CLIENT_ID= 17 | GOOGLE_EMAIL_CLIENT_SECRET= 18 | 19 | LINEAR_API_KEY= 20 | 21 | GITHUB_PERSONAL_ACCESS_TOKEN= 22 | 23 | JIRA_EMAIL= 24 | JIRA_TOKEN= 25 | 26 | NOTION_API_KEY= 27 | 28 | GOOGLE_DRIVE_FOLDER_ID= 29 | GOOGLE_DRIVE_REFRESH_TOKEN= 30 | GOOGLE_DRIVE_CLIENT_ID= 31 | GOOGLE_DRIVE_CLIENT_SECRET= 32 | GOOGLE_DRIVE_SERVICE_ACCOUNT_JSON= 33 | 34 | ONEDRIVE_FOLDER_ID= 35 | ONEDRIVE_REFRESH_TOKEN= 36 | ONEDRIVE_CLIENT_ID= 37 | ONEDRIVE_CLIENT_SECRET= 38 | 39 | SHAREPOINT_ACCOUNT_NAME= 40 | SHAREPOINT_REFRESH_TOKEN= 41 | SHAREPOINT_CLIENT_ID= 42 | SHAREPOINT_CLIENT_SECRET= 43 | 44 | DROPBOX_APP_KEY= 45 | DROPBOX_APP_SECRET= 46 | DROPBOX_REFRESH_TOKEN= 47 | 48 | BOX_CLIENT_ID= 49 | BOX_CLIENT_SECRET= 50 | BOX_REFRESH_TOKEN= 51 | BOX_REDIRECT_URI= 52 | 53 | TWITTER_CONSUMER_API_KEY= 54 | TWITTER_CONSUMER_API_SECRET= 55 | TWITTER_ACCESS_TOKEN_KEY= 56 | TWITTER_ACCESS_TOKEN_SECRET= -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphlit-mcp-server", 3 | "version": "1.0.1", 4 | "description": "Graphlit MCP Server", 5 | "type": "module", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/graphlit/graphlit-mcp-server.git" 9 | }, 10 | "contributors": [ 11 | "Kirk Marple (https://github.com/kirk-marple)" 12 | ], 13 | "bin": { 14 | "graphlit-mcp-server": "dist/index.js" 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "engines": { 20 | "node": ">=18.0.0" 21 | }, 22 | "scripts": { 23 | "start": "node ./dist/index.js", 24 | "dev": "ts-node ./src/index.ts", 25 | "build": "tsc -p tsconfig.json", 26 | "watch": "tsc --watch", 27 | "check": "tsc --noEmit --incremental", 28 | "format": "prettier --write .", 29 | "inspector": "npx @modelcontextprotocol/inspector node dist/index.js" 30 | }, 31 | "keywords": [ 32 | "Graphlit", 33 | "API", 34 | "LLM", 35 | "AI", 36 | "RAG", 37 | "OpenAI", 38 | "PDF", 39 | "parsing", 40 | "preprocessing", 41 | "memory", 42 | "agents", 43 | "agent tools", 44 | "retrieval", 45 | "web scraping", 46 | "knowledge graph", 47 | "MCP" 48 | ], 49 | "author": "Unstruk Data Inc.", 50 | "license": "MIT", 51 | "dependencies": { 52 | "@modelcontextprotocol/sdk": "^1.24.3", 53 | "graphlit-client": "^1.0.20251204001" 54 | }, 55 | "devDependencies": { 56 | "@types/mime-types": "^3.0.1", 57 | "prettier": "^3.7.4", 58 | "typescript": "^5.9.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | build 93 | 94 | dist 95 | 96 | # Gatsby files 97 | .cache/ 98 | # Comment in the public line in if your project uses Gatsby and not Next.js 99 | # https://nextjs.org/blog/next-9-1#public-directory-support 100 | # public 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # vuepress v2.x temp and cache directory 106 | .temp 107 | .cache 108 | 109 | # Docusaurus cache and generated files 110 | .docusaurus 111 | 112 | # Serverless directories 113 | .serverless/ 114 | 115 | # FuseBox cache 116 | .fusebox/ 117 | 118 | # DynamoDB Local files 119 | .dynamodb/ 120 | 121 | # TernJS port file 122 | .tern-port 123 | 124 | # Stores VSCode versions used for testing VSCode extensions 125 | .vscode-test 126 | 127 | # yarn v2 128 | .yarn/cache 129 | .yarn/unplugged 130 | .yarn/build-state.yml 131 | .yarn/install-state.gz 132 | .pnp.* 133 | -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml 2 | 3 | startCommand: 4 | type: stdio 5 | configSchema: 6 | # JSON Schema defining the configuration options for the MCP. 7 | type: object 8 | required: 9 | - organizationId 10 | - environmentId 11 | - jwtSecret 12 | properties: 13 | organizationId: 14 | type: string 15 | default: your-organization-id 16 | description: Graphlit organization ID 17 | environmentId: 18 | type: string 19 | default: your-environment-id 20 | description: Graphlit environment ID 21 | jwtSecret: 22 | type: string 23 | default: your-jwt-secret 24 | description: JWT secret for signing tokens 25 | slackBotToken: 26 | type: string 27 | default: "" 28 | description: Slack bot token (optional) 29 | discordBotToken: 30 | type: string 31 | default: "" 32 | description: Discord bot token (optional) 33 | googleEmailRefreshToken: 34 | type: string 35 | default: "" 36 | description: Google Email refresh token (optional) 37 | googleEmailClientId: 38 | type: string 39 | default: "" 40 | description: Google Email Client ID (optional) 41 | googleEmailClientSecret: 42 | type: string 43 | default: "" 44 | description: Google Email Client Secret (optional) 45 | linearApiKey: 46 | type: string 47 | default: "" 48 | description: Linear API Key (optional) 49 | githubPersonalAccessToken: 50 | type: string 51 | default: "" 52 | description: GitHub Personal Access Token (optional) 53 | jiraEmail: 54 | type: string 55 | default: "" 56 | description: Jira email (optional) 57 | jiraToken: 58 | type: string 59 | default: "" 60 | description: Jira token (optional) 61 | notionApiKey: 62 | type: string 63 | default: "" 64 | description: Notion API Key (optional) 65 | twitterConsumerApiKey: 66 | type: string 67 | default: "" 68 | description: Twitter Consumer API Key (optional) 69 | twitterConsumerApiSecret: 70 | type: string 71 | default: "" 72 | description: Twitter Consumer API Secret (optional) 73 | twitterAccessTokenKey: 74 | type: string 75 | default: "" 76 | description: Twitter Access Token Key (optional) 77 | twitterAccessTokenSecret: 78 | type: string 79 | default: "" 80 | description: Twitter Access Token Secret (optional) 81 | commandFunction: 82 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio. 83 | |- 84 | (config) => ({ 85 | command: 'node', 86 | args: ['build/index.js'], 87 | env: { 88 | GRAPHLIT_ORGANIZATION_ID: config.organizationId, 89 | GRAPHLIT_ENVIRONMENT_ID: config.environmentId, 90 | GRAPHLIT_JWT_SECRET: config.jwtSecret, 91 | SLACK_BOT_TOKEN: config.slackBotToken || '', 92 | DISCORD_BOT_TOKEN: config.discordBotToken || '', 93 | GOOGLE_EMAIL_REFRESH_TOKEN: config.googleEmailRefreshToken || '', 94 | GOOGLE_EMAIL_CLIENT_ID: config.googleEmailClientId || '', 95 | GOOGLE_EMAIL_CLIENT_SECRET: config.googleEmailClientSecret || '', 96 | LINEAR_API_KEY: config.linearApiKey || '', 97 | GITHUB_PERSONAL_ACCESS_TOKEN: config.githubPersonalAccessToken || '', 98 | JIRA_EMAIL: config.jiraEmail || '', 99 | JIRA_TOKEN: config.jiraToken || '', 100 | NOTION_API_KEY: config.notionApiKey || '', 101 | TWITTER_CONSUMER_API_KEY: config.twitterConsumerApiKey || '', 102 | TWITTER_CONSUMER_API_SECRET: config.twitterConsumerApiSecret || '', 103 | TWITTER_ACCESS_TOKEN_KEY: config.twitterAccessTokenKey || '', 104 | TWITTER_ACCESS_TOKEN_SECRET: config.twitterAccessTokenSecret || '', 105 | } 106 | }) 107 | exampleConfig: 108 | organizationId: your-organization-id 109 | environmentId: your-environment-id 110 | jwtSecret: your-jwt-secret 111 | slackBotToken: example-slack-bot-token 112 | discordBotToken: example-discord-bot-token 113 | googleEmailRefreshToken: example-google-refresh-token 114 | googleEmailClientId: example-google-client-id 115 | googleEmailClientSecret: example-google-client-secret 116 | linearApiKey: example-linear-api-key 117 | githubPersonalAccessToken: example-github-pat 118 | jiraEmail: example-jira-email 119 | jiraToken: example-jira-token 120 | notionApiKey: example-notion-api-key, 121 | twitterConsumerApiKey: example-twitter-consumer-api-key 122 | twitterConsumerApiSecret: example-twitter-consumer-api-secret 123 | twitterAccessTokenKey: example-twitter-access-token-key 124 | twitterAccessTokenSecret: example-twitter-access-token-secret 125 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 4 | import { registerResources } from "./resources.js"; 5 | import { registerTools } from "./tools.js"; 6 | 7 | const DEFAULT_INSTRUCTIONS = ` 8 | You are provided a set of MCP tools and resources that integrate with the [Graphlit](https://www.graphlit.com) Platform. 9 | 10 | To use each of the Graphlit MCP tools, there may be environment variables which are required to be configured in your MCP client. These are described in the description for each tool. 11 | These must be configured in the MCP client YAML or JSON configuration file before you can use the tools. *Do not* set these directly in your Terminal or shell environment. 12 | 13 | Graphlit is an LLM-enabled knowledge API platform, which supports these resources: 14 | - project: container for ingested contents, which can be configured with a default workflow 15 | - contents: all ingested files, web pages, messages, etc.; also includes short-term 'memory' contents 16 | - feeds: data connectors which ingest contents 17 | - collections: named groups of contents 18 | - conversations: chat message history of LLM conversation, which uses RAG pipeline for content retrieval 19 | - workflows: how content is handled during the ingestion process 20 | - specifications: LLM configuration presets, used by workflows and conversations 21 | 22 | Identifiers for all resources are unique within the Graphlit project, and are formatted as GUIDs. 23 | 24 | You have access to one and only one Graphlit project, which can optionally be configured with a workflow to guide the document preparation and entity extraction of ingested content. 25 | The Graphlit project is non-deletable, but you can create and delete contents, feeds, collections, conversations, specifications and workflows within the project. 26 | 27 | You can query the Graphlit project resource for the credits used, LLM tokens used, and the available project quota. By default, credits cost USD$0.10, and are discounted on higher paid tiers. 28 | 29 | With this Graphlit MCP Server, you can ingest anything from Slack, Discord, websites, Notion, Google Drive, email, Jira, Linear or GitHub into a Graphlit project - and then search and retrieve relevant knowledge within an MCP client like Cursor, Windsurf or Cline. 30 | 31 | Documents (PDF, DOCX, PPTX, etc.) and HTML web pages will be extracted to Markdown upon ingestion. Audio and video files will be transcribed upon ingestion. 32 | 33 | ## Best Practices: 34 | 1. Always look for matching resources before you try to call any tools. 35 | For example, "have i configured any graphlit workflows?", you should check for workflow resources before trying to call any other tools. 36 | 2. Don't use 'retrieveSources' to locate contents, when you have already added the contents into a collection. In that case, first retrieve the collection resource, which contains the content resources. 37 | 3. Only call the 'configureProject' tool when the user explicitly asks to configure their Graphlit project defaults. 38 | 4. Never infer, guess at or hallucinate any URLs. Always retrieve the latest content resources in order to get downloadable URLs. 39 | 5. Use 'ingestMemory' to save short-term memories, such as temporary notes or intermediate state for research. Use 'ingestText' to store long-term knowledge, such as Markdown results from research. 40 | 6. Always use 'PODSCAN' web search type when searching for podcast episodes, podcast appearances, etc. 41 | 7. Prioritize using feeds, rather than 'ingestUrl', when you want to ingest a website. Feeds are more efficient and faster than using 'ingestUrl'. 42 | If you receive a request to ingest a GitHub URL, use the 'ingestGitHubFiles' tool to ingest the repository, rather than using 'ingestUrl'. 43 | Always attempt to use the most-specific tool for the task at hand. 44 | 45 | ## Short-term vs Long-term Memory: 46 | You can perform scatter-gather operations where you save short-term memories after each workflow step, and then gather relevant memories prior to the moving onto the next step. 47 | Leverage short-term memories when evaluating the results of a workflow step, and then use long-term memories to store the final results of your workflow. 48 | You can collect memories in collections, and then use the 'queryContents' tool to retrieve the 'memory' contents by the collection. This will help you to keep track of your progress and avoid losing any important information. 49 | 50 | If you have any trouble with this Graphlit MCP Server, join our [Discord](https://discord.gg/ygFmfjy3Qx) community for support. 51 | `; 52 | 53 | export const server = new McpServer( 54 | { 55 | name: "Graphlit MCP Server", 56 | version: "1.0.0", 57 | }, 58 | { 59 | instructions: DEFAULT_INSTRUCTIONS, 60 | } 61 | ); 62 | 63 | registerResources(server); 64 | registerTools(server); 65 | 66 | async function runServer() { 67 | try { 68 | console.error("Attempting to start Graphlit MCP Server."); 69 | 70 | const transport = new StdioServerTransport(); 71 | await server.connect(transport); 72 | 73 | console.error("Successfully started Graphlit MCP Server."); 74 | } catch (error) { 75 | console.error("Failed to start Graphlit MCP Server.", error); 76 | 77 | process.exit(1); 78 | } 79 | } 80 | 81 | runServer().catch((error) => { 82 | console.error("Failed to start Graphlit MCP Server.", error); 83 | 84 | process.exit(1); 85 | }); 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://badge.fury.io/js/graphlit-mcp-server.svg)](https://badge.fury.io/js/graphlit-mcp-server) 2 | [![smithery badge](https://smithery.ai/badge/@graphlit/graphlit-mcp-server)](https://smithery.ai/server/@graphlit/graphlit-mcp-server) 3 | 4 | # Model Context Protocol (MCP) Server for Graphlit Platform 5 | 6 | ## Overview 7 | 8 | The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. This document outlines the setup process and provides a basic example of using the client. 9 | 10 | Ingest anything from Slack, Discord, websites, Google Drive, email, Jira, Linear or GitHub into a Graphlit project - and then search and retrieve relevant knowledge within an MCP client like Cursor, Windsurf, Goose or Cline. 11 | 12 | Your Graphlit project acts as a searchable, and RAG-ready knowledge base across all your developer and product management tools. 13 | 14 | Documents (PDF, DOCX, PPTX, etc.) and HTML web pages will be extracted to Markdown upon ingestion. Audio and video files will be transcribed upon ingestion. 15 | 16 | Web crawling and web search are built-in as MCP tools, with no need to integrate other tools like Firecrawl, Exa, etc. separately. 17 | 18 | You can read more about the MCP Server use cases and features on our [blog](https://www.graphlit.com/blog/graphlit-mcp-server). 19 | 20 | Watch our latest [YouTube video](https://www.youtube.com/watch?v=Or-QqonvcAs&t=4s) on using the Graphlit MCP Server with the Goose MCP client. 21 | 22 | For any questions on using the MCP Server, please join our [Discord](https://discord.gg/ygFmfjy3Qx) community and post on the #mcp channel. 23 | 24 | 25 | graphlit-mcp-server MCP server 26 | 27 | 28 | ## Tools 29 | 30 | ### Retrieval 31 | 32 | - Query Contents 33 | - Query Collections 34 | - Query Feeds 35 | - Query Conversations 36 | - Retrieve Relevant Sources 37 | - Retrieve Similar Images 38 | - Visually Describe Image 39 | 40 | ### RAG 41 | 42 | - Prompt LLM Conversation 43 | 44 | ### Extraction 45 | 46 | - Extract Structured JSON from Text 47 | 48 | ### Publishing 49 | 50 | - Publish as Audio (ElevenLabs Audio) 51 | - Publish as Image (OpenAI Image Generation) 52 | 53 | ### Ingestion 54 | 55 | - Files 56 | - Web Pages 57 | - Messages 58 | - Posts 59 | - Emails 60 | - Issues 61 | - Text 62 | - Memory (Short-Term) 63 | 64 | ### Data Connectors 65 | 66 | - Microsoft Outlook email 67 | - Google Mail 68 | - Notion 69 | - Reddit 70 | - Linear 71 | - Jira 72 | - GitHub Issues 73 | - Google Drive 74 | - OneDrive 75 | - SharePoint 76 | - Dropbox 77 | - Box 78 | - GitHub 79 | - Slack 80 | - Microsoft Teams 81 | - Discord 82 | - Twitter/X 83 | - Podcasts (RSS) 84 | 85 | ### Web 86 | 87 | - Web Crawling 88 | - Web Search (including Podcast Search) 89 | - Web Mapping 90 | - Screenshot Page 91 | 92 | ### Notifications 93 | 94 | - Slack 95 | - Email 96 | - Webhook 97 | - Twitter/X 98 | 99 | ### Operations 100 | 101 | - Configure Project 102 | - Create Collection 103 | - Add Contents to Collection 104 | - Remove Contents from Collection 105 | - Delete Collection(s) 106 | - Delete Feed(s) 107 | - Delete Content(s) 108 | - Delete Conversation(s) 109 | - Is Feed Done? 110 | - Is Content Done? 111 | 112 | ### Enumerations 113 | 114 | - List Slack Channels 115 | - List Microsoft Teams Teams 116 | - List Microsoft Teams Channels 117 | - List SharePoint Libraries 118 | - List SharePoint Folders 119 | - List Linear Projects 120 | - List Notion Databases 121 | - List Notion Pages 122 | - List Dropbox Folders 123 | - List Box Folders 124 | - List Discord Guilds 125 | - List Discord Channels 126 | - List Google Calendars 127 | - List Microsoft Calendars 128 | 129 | ## Resources 130 | 131 | - Project 132 | - Contents 133 | - Feeds 134 | - Collections (of Content) 135 | - Workflows 136 | - Conversations 137 | - Specifications 138 | 139 | ## Prerequisites 140 | 141 | Before you begin, ensure you have the following: 142 | 143 | - Node.js installed on your system (recommended version 18.x or higher). 144 | - An active account on the [Graphlit Platform](https://portal.graphlit.dev) with access to the API settings dashboard. 145 | 146 | ## Configuration 147 | 148 | The Graphlit MCP Server supports environment variables to be set for authentication and configuration: 149 | 150 | - `GRAPHLIT_ENVIRONMENT_ID`: Your environment ID. 151 | - `GRAPHLIT_ORGANIZATION_ID`: Your organization ID. 152 | - `GRAPHLIT_JWT_SECRET`: Your JWT secret for signing the JWT token. 153 | 154 | You can find these values in the API settings dashboard on the [Graphlit Platform](https://portal.graphlit.dev). 155 | 156 | ## Installation 157 | 158 | ### Installing via VS Code 159 | 160 | For quick installation, use one of the one-click install buttons below: 161 | 162 | [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=graphlit&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22organization_id%22%2C%22description%22%3A%22Graphlit%20Organization%20ID%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22environment_id%22%2C%22description%22%3A%22Graphlit%20Environment%20ID%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22jwt_secret%22%2C%22description%22%3A%22Graphlit%20JWT%20Secret%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22graphlit-mcp-server%22%5D%2C%22env%22%3A%7B%22GRAPHLIT_ORGANIZATION_ID%22%3A%22%24%7Binput%3Aorganization_id%7D%22%2C%22GRAPHLIT_ENVIRONMENT_ID%22%3A%22%24%7Binput%3Aenvironment_id%7D%22%2C%22GRAPHLIT_JWT_SECRET%22%3A%22%24%7Binput%3Ajwt_secret%7D%22%7D%7D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=graphlit&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22organization_id%22%2C%22description%22%3A%22Graphlit%20Organization%20ID%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22environment_id%22%2C%22description%22%3A%22Graphlit%20Environment%20ID%22%2C%22password%22%3Atrue%7D%2C%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22jwt_secret%22%2C%22description%22%3A%22Graphlit%20JWT%20Secret%22%2C%22password%22%3Atrue%7D%5D&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22graphlit-mcp-server%22%5D%2C%22env%22%3A%7B%22GRAPHLIT_ORGANIZATION_ID%22%3A%22%24%7Binput%3Aorganization_id%7D%22%2C%22GRAPHLIT_ENVIRONMENT_ID%22%3A%22%24%7Binput%3Aenvironment_id%7D%22%2C%22GRAPHLIT_JWT_SECRET%22%3A%22%24%7Binput%3Ajwt_secret%7D%22%7D%7D&quality=insiders) 163 | 164 | For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`. 165 | 166 | Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. 167 | 168 | > Note that the `mcp` key is not needed in the `.vscode/mcp.json` file. 169 | 170 | ```json 171 | { 172 | "mcp": { 173 | "inputs": [ 174 | { 175 | "type": "promptString", 176 | "id": "organization_id", 177 | "description": "Graphlit Organization ID", 178 | "password": true 179 | }, 180 | { 181 | "type": "promptString", 182 | "id": "environment_id", 183 | "description": "Graphlit Environment ID", 184 | "password": true 185 | }, 186 | { 187 | "type": "promptString", 188 | "id": "jwt_secret", 189 | "description": "Graphlit JWT Secret", 190 | "password": true 191 | } 192 | ], 193 | "servers": { 194 | "graphlit": { 195 | "command": "npx", 196 | "args": ["-y", "graphlit-mcp-server"], 197 | "env": { 198 | "GRAPHLIT_ORGANIZATION_ID": "${input:organization_id}", 199 | "GRAPHLIT_ENVIRONMENT_ID": "${input:environment_id}", 200 | "GRAPHLIT_JWT_SECRET": "${input:jwt_secret}" 201 | } 202 | } 203 | } 204 | } 205 | } 206 | ``` 207 | 208 | ### Installing via Windsurf 209 | 210 | To install graphlit-mcp-server in Windsurf IDE application, Cline should use NPX: 211 | 212 | ```bash 213 | npx -y graphlit-mcp-server 214 | ``` 215 | 216 | Your mcp_config.json file should be configured similar to: 217 | 218 | ``` 219 | { 220 | "mcpServers": { 221 | "graphlit-mcp-server": { 222 | "command": "npx", 223 | "args": [ 224 | "-y", 225 | "graphlit-mcp-server" 226 | ], 227 | "env": { 228 | "GRAPHLIT_ORGANIZATION_ID": "your-organization-id", 229 | "GRAPHLIT_ENVIRONMENT_ID": "your-environment-id", 230 | "GRAPHLIT_JWT_SECRET": "your-jwt-secret", 231 | } 232 | } 233 | } 234 | } 235 | ``` 236 | 237 | ### Installing via Cline 238 | 239 | To install graphlit-mcp-server in Cline IDE application, Cline should use NPX: 240 | 241 | ```bash 242 | npx -y graphlit-mcp-server 243 | ``` 244 | 245 | Your cline_mcp_settings.json file should be configured similar to: 246 | 247 | ``` 248 | { 249 | "mcpServers": { 250 | "graphlit-mcp-server": { 251 | "command": "npx", 252 | "args": [ 253 | "-y", 254 | "graphlit-mcp-server" 255 | ], 256 | "env": { 257 | "GRAPHLIT_ORGANIZATION_ID": "your-organization-id", 258 | "GRAPHLIT_ENVIRONMENT_ID": "your-environment-id", 259 | "GRAPHLIT_JWT_SECRET": "your-jwt-secret", 260 | } 261 | } 262 | } 263 | } 264 | ``` 265 | 266 | ### Installing via Cursor 267 | 268 | To install graphlit-mcp-server in Cursor IDE application, Cursor should use NPX: 269 | 270 | ```bash 271 | npx -y graphlit-mcp-server 272 | ``` 273 | 274 | Your mcp.json file should be configured similar to: 275 | 276 | ``` 277 | { 278 | "mcpServers": { 279 | "graphlit-mcp-server": { 280 | "command": "npx", 281 | "args": [ 282 | "-y", 283 | "graphlit-mcp-server" 284 | ], 285 | "env": { 286 | "GRAPHLIT_ORGANIZATION_ID": "your-organization-id", 287 | "GRAPHLIT_ENVIRONMENT_ID": "your-environment-id", 288 | "GRAPHLIT_JWT_SECRET": "your-jwt-secret", 289 | } 290 | } 291 | } 292 | } 293 | ``` 294 | 295 | ### Installing via Smithery 296 | 297 | To install graphlit-mcp-server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@graphlit/graphlit-mcp-server): 298 | 299 | ```bash 300 | npx -y @smithery/cli install @graphlit/graphlit-mcp-server --client claude 301 | ``` 302 | 303 | ### Installing manually 304 | 305 | To use the Graphlit MCP Server in any MCP client application, use: 306 | 307 | ``` 308 | { 309 | "mcpServers": { 310 | "graphlit-mcp-server": { 311 | "command": "npx", 312 | "args": [ 313 | "-y", 314 | "graphlit-mcp-server" 315 | ], 316 | "env": { 317 | "GRAPHLIT_ORGANIZATION_ID": "your-organization-id", 318 | "GRAPHLIT_ENVIRONMENT_ID": "your-environment-id", 319 | "GRAPHLIT_JWT_SECRET": "your-jwt-secret", 320 | } 321 | } 322 | } 323 | } 324 | ``` 325 | 326 | Optionally, you can configure the credentials for data connectors, such as Slack, Google Email and Notion. 327 | Only GRAPHLIT_ORGANIZATION_ID, GRAPHLIT_ENVIRONMENT_ID and GRAPHLIT_JWT_SECRET are required. 328 | 329 | ``` 330 | { 331 | "mcpServers": { 332 | "graphlit-mcp-server": { 333 | "command": "npx", 334 | "args": [ 335 | "-y", 336 | "graphlit-mcp-server" 337 | ], 338 | "env": { 339 | "GRAPHLIT_ORGANIZATION_ID": "your-organization-id", 340 | "GRAPHLIT_ENVIRONMENT_ID": "your-environment-id", 341 | "GRAPHLIT_JWT_SECRET": "your-jwt-secret", 342 | "SLACK_BOT_TOKEN": "your-slack-bot-token", 343 | "DISCORD_BOT_TOKEN": "your-discord-bot-token", 344 | "TWITTER_TOKEN": "your-twitter-token", 345 | "GOOGLE_EMAIL_REFRESH_TOKEN": "your-google-refresh-token", 346 | "GOOGLE_EMAIL_CLIENT_ID": "your-google-client-id", 347 | "GOOGLE_EMAIL_CLIENT_SECRET": "your-google-client-secret", 348 | "LINEAR_API_KEY": "your-linear-api-key", 349 | "GITHUB_PERSONAL_ACCESS_TOKEN": "your-github-pat", 350 | "JIRA_EMAIL": "your-jira-email", 351 | "JIRA_TOKEN": "your-jira-token", 352 | "NOTION_API_KEY": "your-notion-api-key" 353 | } 354 | } 355 | } 356 | } 357 | ``` 358 | 359 | NOTE: when running 'npx' on Windows, you may need to explicitly call npx via the command prompt. 360 | 361 | ``` 362 | "command": "C:\\Windows\\System32\\cmd.exe /c npx" 363 | ``` 364 | 365 | ## Support 366 | 367 | Please refer to the [Graphlit API Documentation](https://docs.graphlit.dev/). 368 | 369 | For support with the Graphlit MCP Server, please submit a [GitHub Issue](https://github.com/graphlit/graphlit-mcp-server/issues). 370 | 371 | For further support with the Graphlit Platform, please join our [Discord](https://discord.gg/ygFmfjy3Qx) community. 372 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | import { Graphlit } from "graphlit-client"; 2 | import { 3 | McpServer, 4 | ResourceTemplate, 5 | } from "@modelcontextprotocol/sdk/server/mcp.js"; 6 | import { 7 | ContentTypes, 8 | ContentFilter, 9 | ConversationFilter, 10 | EntityState, 11 | GetContentQuery, 12 | GetConversationQuery, 13 | } from "graphlit-client/dist/generated/graphql-types"; 14 | 15 | export function registerResources(server: McpServer) { 16 | server.resource( 17 | "Conversations list: Returns list of conversation resources.", 18 | new ResourceTemplate("conversations://", { 19 | list: async (extra) => { 20 | const client = new Graphlit(); 21 | 22 | const filter: ConversationFilter = {}; 23 | 24 | try { 25 | const response = await client.queryConversations(filter); 26 | 27 | return { 28 | resources: (response.conversations?.results || []) 29 | .filter((content) => content !== null) 30 | .map((conversation) => ({ 31 | name: conversation.name, 32 | uri: `conversations://${conversation.id}`, 33 | mimeType: "text/markdown", 34 | })), 35 | }; 36 | } catch (error) { 37 | console.error("Error fetching conversation list:", error); 38 | return { resources: [] }; 39 | } 40 | }, 41 | }), 42 | async (uri, variables) => { 43 | return { 44 | contents: [], 45 | }; 46 | } 47 | ); 48 | 49 | server.resource( 50 | "Conversation: Returns LLM conversation messages. Accepts conversation resource URI, i.e. conversations://{id}, where 'id' is a conversation identifier.", 51 | new ResourceTemplate("conversations://{id}", { list: undefined }), 52 | async (uri: URL, variables) => { 53 | const id = variables.id as string; 54 | const client = new Graphlit(); 55 | 56 | try { 57 | const response = await client.getConversation(id); 58 | 59 | const content = response.conversation; 60 | 61 | return { 62 | contents: [ 63 | { 64 | uri: uri.toString(), 65 | text: formatConversation(response), 66 | mimeType: "text/markdown", 67 | }, 68 | ], 69 | }; 70 | } catch (error) { 71 | console.error("Error fetching conversation:", error); 72 | return { 73 | contents: [], 74 | }; 75 | } 76 | } 77 | ); 78 | 79 | server.resource( 80 | "Feeds: Returns list of feed resources.", 81 | new ResourceTemplate("feeds://", { 82 | list: async (extra) => { 83 | const client = new Graphlit(); 84 | 85 | try { 86 | const response = await client.queryFeeds(); 87 | 88 | return { 89 | resources: (response.feeds?.results || []) 90 | .filter((feed) => feed !== null) 91 | .map((feed) => ({ 92 | name: feed.name, 93 | uri: `feeds://${feed.id}`, 94 | })), 95 | }; 96 | } catch (error) { 97 | console.error("Error fetching feed list:", error); 98 | return { resources: [] }; 99 | } 100 | }, 101 | }), 102 | async (uri, variables) => { 103 | return { 104 | contents: [], 105 | }; 106 | } 107 | ); 108 | 109 | server.resource( 110 | "Feed: Returns feed metadata. Accepts content resource URI, i.e. feeds://{id}, where 'id' is a feed identifier.", 111 | new ResourceTemplate("feeds://{id}", { list: undefined }), 112 | async (uri: URL, variables) => { 113 | const id = variables.id as string; 114 | const client = new Graphlit(); 115 | 116 | try { 117 | const response = await client.getFeed(id); 118 | 119 | return { 120 | contents: [ 121 | { 122 | uri: uri.toString(), 123 | text: JSON.stringify( 124 | { 125 | id: response.feed?.id, 126 | name: response.feed?.name, 127 | type: response.feed?.type, 128 | readCount: response.feed?.readCount, 129 | creationDate: response.feed?.creationDate, 130 | lastReadDate: response.feed?.lastReadDate, 131 | state: response.feed?.state, 132 | error: response.feed?.error, 133 | }, 134 | null, 135 | 2 136 | ), 137 | mimeType: "application/json", 138 | }, 139 | ], 140 | }; 141 | } catch (error) { 142 | console.error("Error fetching feed:", error); 143 | return { 144 | contents: [], 145 | }; 146 | } 147 | } 148 | ); 149 | 150 | server.resource( 151 | "Collections: Returns list of collection resources.", 152 | new ResourceTemplate("collections://", { 153 | list: async (extra) => { 154 | const client = new Graphlit(); 155 | 156 | try { 157 | const response = await client.queryCollections(); 158 | 159 | return { 160 | resources: (response.collections?.results || []) 161 | .filter((collection) => collection !== null) 162 | .map((collection) => ({ 163 | name: collection.name, 164 | uri: `collections://${collection.id}`, 165 | })), 166 | }; 167 | } catch (error) { 168 | console.error("Error fetching collection list:", error); 169 | return { resources: [] }; 170 | } 171 | }, 172 | }), 173 | async (uri, variables) => { 174 | return { 175 | contents: [], 176 | }; 177 | } 178 | ); 179 | 180 | server.resource( 181 | "Collection: Returns collection metadata and list of content resources. Accepts collection resource URI, i.e. collections://{id}, where 'id' is a collection identifier.", 182 | new ResourceTemplate("collections://{id}", { list: undefined }), 183 | async (uri: URL, variables) => { 184 | const id = variables.id as string; 185 | const client = new Graphlit(); 186 | 187 | try { 188 | const response = await client.getCollection(id); 189 | return { 190 | contents: [ 191 | { 192 | uri: uri.toString(), 193 | text: JSON.stringify( 194 | { 195 | id: response.collection?.id, 196 | name: response.collection?.name, 197 | contents: (response.collection?.contents || []) 198 | .filter((content) => content !== null) 199 | .map((content) => `contents://${content.id}`), 200 | }, 201 | null, 202 | 2 203 | ), 204 | mimeType: "application/json", 205 | }, 206 | ], 207 | }; 208 | } catch (error) { 209 | console.error("Error fetching collection:", error); 210 | return { 211 | contents: [], 212 | }; 213 | } 214 | } 215 | ); 216 | 217 | server.resource( 218 | "Contents list: Returns list of content resources.", 219 | new ResourceTemplate("contents://", { 220 | list: async (extra) => { 221 | const client = new Graphlit(); 222 | 223 | const filter: ContentFilter = { 224 | states: [EntityState.Finished], // filter on finished contents only 225 | }; 226 | 227 | try { 228 | const response = await client.queryContents(filter); 229 | 230 | return { 231 | resources: (response.contents?.results || []) 232 | .filter((content) => content !== null) 233 | .map((content) => ({ 234 | name: content.name, 235 | description: content.description || "", 236 | uri: `contents://${content.id}`, 237 | mimeType: content.mimeType || "text/markdown", 238 | })), 239 | }; 240 | } catch (error) { 241 | console.error("Error fetching content list:", error); 242 | return { resources: [] }; 243 | } 244 | }, 245 | }), 246 | async (uri, variables) => { 247 | return { 248 | contents: [], 249 | }; 250 | } 251 | ); 252 | 253 | server.resource( 254 | "Content: Returns content metadata and complete Markdown text. Accepts content resource URI, i.e. contents://{id}, where 'id' is a content identifier.", 255 | new ResourceTemplate("contents://{id}", { list: undefined }), 256 | async (uri: URL, variables) => { 257 | const id = variables.id as string; 258 | const client = new Graphlit(); 259 | 260 | try { 261 | const response = await client.getContent(id); 262 | 263 | const content = response.content; 264 | 265 | return { 266 | contents: [ 267 | { 268 | uri: uri.toString(), 269 | text: formatContent(response), 270 | mimeType: "text/markdown", 271 | }, 272 | ], 273 | }; 274 | } catch (error) { 275 | console.error("Error fetching content:", error); 276 | return { 277 | contents: [], 278 | }; 279 | } 280 | } 281 | ); 282 | 283 | server.resource( 284 | "Workflows: Returns list of workflow resources.", 285 | new ResourceTemplate("workflows://", { 286 | list: async (extra) => { 287 | const client = new Graphlit(); 288 | 289 | try { 290 | const response = await client.queryWorkflows(); 291 | 292 | return { 293 | resources: (response.workflows?.results || []) 294 | .filter((workflow) => workflow !== null) 295 | .map((workflow) => ({ 296 | name: workflow.name, 297 | uri: `workflows://${workflow.id}`, 298 | })), 299 | }; 300 | } catch (error) { 301 | console.error("Error fetching workflow list:", error); 302 | return { resources: [] }; 303 | } 304 | }, 305 | }), 306 | async (uri, variables) => { 307 | return { 308 | contents: [], 309 | }; 310 | } 311 | ); 312 | 313 | server.resource( 314 | "Workflow: Returns workflow metadata. Accepts workflow resource URI, i.e. workflows://{id}, where 'id' is a workflow identifier.", 315 | new ResourceTemplate("workflows://{id}", { list: undefined }), 316 | async (uri: URL, variables) => { 317 | const id = variables.id as string; 318 | const client = new Graphlit(); 319 | 320 | try { 321 | const response = await client.getWorkflow(id); 322 | return { 323 | contents: [ 324 | { 325 | uri: uri.toString(), 326 | text: JSON.stringify(response.workflow, null, 2), 327 | mimeType: "application/json", 328 | }, 329 | ], 330 | }; 331 | } catch (error) { 332 | console.error("Error fetching workflow:", error); 333 | return { 334 | contents: [], 335 | }; 336 | } 337 | } 338 | ); 339 | 340 | server.resource( 341 | "Specifications: Returns list of specification resources.", 342 | new ResourceTemplate("specifications://", { 343 | list: async (extra) => { 344 | const client = new Graphlit(); 345 | 346 | try { 347 | const response = await client.querySpecifications(); 348 | 349 | return { 350 | resources: (response.specifications?.results || []) 351 | .filter((specification) => specification !== null) 352 | .map((specification) => ({ 353 | name: specification.name, 354 | uri: `specifications://${specification.id}`, 355 | })), 356 | }; 357 | } catch (error) { 358 | console.error("Error fetching specification list:", error); 359 | return { resources: [] }; 360 | } 361 | }, 362 | }), 363 | async (uri, variables) => { 364 | return { 365 | contents: [], 366 | }; 367 | } 368 | ); 369 | 370 | server.resource( 371 | "Specification: Returns specification metadata. Accepts specification resource URI, i.e. specifications://{id}, where 'id' is a specification identifier.", 372 | new ResourceTemplate("specifications://{id}", { list: undefined }), 373 | async (uri: URL, variables) => { 374 | const id = variables.id as string; 375 | const client = new Graphlit(); 376 | 377 | try { 378 | const response = await client.getSpecification(id); 379 | return { 380 | contents: [ 381 | { 382 | uri: uri.toString(), 383 | text: JSON.stringify(response.specification, null, 2), 384 | mimeType: "application/json", 385 | }, 386 | ], 387 | }; 388 | } catch (error) { 389 | console.error("Error fetching specification:", error); 390 | return { 391 | contents: [], 392 | }; 393 | } 394 | } 395 | ); 396 | 397 | server.resource( 398 | "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.", 399 | new ResourceTemplate("projects://", { list: undefined }), 400 | async (uri: URL, variables) => { 401 | const id = variables.id as string; 402 | const client = new Graphlit(); 403 | 404 | try { 405 | const startDate = new Date(Date.now() - 24 * 60 * 60 * 1000); // 1 day ago 406 | const duration = "P1D"; // ISO duration for 1 day 407 | 408 | const cresponse = await client.queryProjectCredits( 409 | startDate.toISOString(), 410 | duration 411 | ); 412 | const credits = cresponse?.credits; 413 | 414 | const tresponse = await client.queryProjectTokens( 415 | startDate.toISOString(), 416 | duration 417 | ); 418 | const tokens = tresponse?.tokens; 419 | 420 | const response = await client.getProject(); 421 | 422 | return { 423 | contents: [ 424 | { 425 | uri: uri.toString(), 426 | text: JSON.stringify( 427 | { 428 | name: response.project?.name, 429 | workflow: response.project?.workflow, 430 | quota: response.project?.quota, 431 | credits: credits, 432 | tokens: tokens, 433 | }, 434 | null, 435 | 2 436 | ), 437 | mimeType: "application/json", 438 | }, 439 | ], 440 | }; 441 | } catch (error) { 442 | console.error("Error fetching project:", error); 443 | return { 444 | contents: [], 445 | }; 446 | } 447 | } 448 | ); 449 | } 450 | 451 | function formatConversation(response: GetConversationQuery): string { 452 | const results: string[] = []; 453 | 454 | const conversation = response.conversation; 455 | 456 | if (!conversation) { 457 | return ""; 458 | } 459 | 460 | // Basic conversation details 461 | results.push(`**Conversation ID:** ${conversation.id}`); 462 | 463 | // Messages 464 | if (conversation.messages?.length) { 465 | conversation.messages.forEach((message) => { 466 | results.push(`${message?.role}:\n${message?.message}` || ""); 467 | 468 | if (message?.citations?.length) { 469 | message.citations.forEach((citation) => { 470 | results.push( 471 | `**Cited Source [${citation?.index}]**: contents://${citation?.content?.id}` 472 | ); 473 | results.push(`**Cited Text**:\n${citation?.text || ""}`); 474 | }); 475 | } 476 | 477 | results.push("\n---\n"); 478 | }); 479 | } 480 | 481 | return results.join("\n"); 482 | } 483 | 484 | function formatContent(response: GetContentQuery): string { 485 | const results: string[] = []; 486 | 487 | const content = response.content; 488 | 489 | if (!content) { 490 | return ""; 491 | } 492 | 493 | // Basic content details 494 | results.push(`**Content ID:** ${content.id}`); 495 | 496 | if (content.type === ContentTypes.File) { 497 | results.push(`**File Type:** [${content.fileType}]`); 498 | results.push(`**File Name:** ${content.fileName}`); 499 | } else { 500 | results.push(`**Type:** [${content.type}]`); 501 | if ( 502 | content.type !== ContentTypes.Page && 503 | content.type !== ContentTypes.Email 504 | ) { 505 | results.push(`**Name:** ${content.name}`); 506 | } 507 | } 508 | 509 | // Optional metadata 510 | 511 | // 512 | // REVIEW: Not sure if source URI is useful for MCP client 513 | // 514 | //if (content.uri) results.push(`**URI:** ${content.uri}`); 515 | 516 | if (content.masterUri) 517 | results.push(`**Downloadable Original:** ${content.masterUri}`); 518 | if (content.imageUri) 519 | results.push(`**Downloadable Image:** ${content.imageUri}`); 520 | if (content.audioUri) 521 | results.push(`**Downloadable Audio:** ${content.audioUri}`); 522 | 523 | if (content.creationDate) 524 | results.push(`**Ingestion Date:** ${content.creationDate}`); 525 | if (content.originalDate) 526 | results.push(`**Author Date:** ${content.originalDate}`); 527 | 528 | // Issue details 529 | if (content.issue) { 530 | const issue = content.issue; 531 | const issueAttributes = [ 532 | ["Title", issue.title], 533 | ["Identifier", issue.identifier], 534 | ["Type", issue.type], 535 | ["Project", issue.project], 536 | ["Team", issue.team], 537 | ["Status", issue.status], 538 | ["Priority", issue.priority], 539 | ]; 540 | results.push( 541 | ...issueAttributes 542 | .filter(([_, value]) => value) 543 | .map(([label, value]) => `**${label}:** ${value}`) 544 | ); 545 | 546 | if (issue.labels?.length) { 547 | results.push(`**Labels:** ${issue.labels.join(", ")}`); 548 | } 549 | } 550 | 551 | // Email details 552 | if (content.email) { 553 | const email = content.email; 554 | const formatRecipient = (r: any) => `${r.name} <${r.email}>`; 555 | 556 | const emailAttributes = [ 557 | ["Subject", email.subject], 558 | ["Sensitivity", email.sensitivity], 559 | ["Priority", email.priority], 560 | ["Importance", email.importance], 561 | ["Labels", email.labels?.join(", ")], 562 | ["To", email.to?.map(formatRecipient).join(", ")], 563 | ["From", email.from?.map(formatRecipient).join(", ")], 564 | ["CC", email.cc?.map(formatRecipient).join(", ")], 565 | ["BCC", email.bcc?.map(formatRecipient).join(", ")], 566 | ]; 567 | results.push( 568 | ...emailAttributes 569 | .filter(([_, value]) => value) 570 | .map(([label, value]) => `**${label}:** ${value}`) 571 | ); 572 | } 573 | 574 | // Document details 575 | if (content.document) { 576 | const doc = content.document; 577 | if (doc.title) results.push(`**Title:** ${doc.title}`); 578 | if (doc.author) results.push(`**Author:** ${doc.author}`); 579 | } 580 | 581 | // Audio details 582 | if (content.audio) { 583 | const audio = content.audio; 584 | if (audio.title) results.push(`**Title:** ${audio.title}`); 585 | if (audio.author) results.push(`**Host:** ${audio.author}`); 586 | if (audio.episode) results.push(`**Episode:** ${audio.episode}`); 587 | if (audio.series) results.push(`**Series:** ${audio.series}`); 588 | } 589 | 590 | // Image details 591 | if (content.image) { 592 | const image = content.image; 593 | if (image.description) 594 | results.push(`**Description:** ${image.description}`); 595 | if (image.software) results.push(`**Software:** ${image.software}`); 596 | if (image.make) results.push(`**Make:** ${image.make}`); 597 | if (image.model) results.push(`**Model:** ${image.model}`); 598 | } 599 | 600 | // Collections 601 | if (content.collections) { 602 | results.push( 603 | ...content.collections 604 | .filter((collection) => collection !== null) 605 | .slice(0, 100) 606 | .map( 607 | (collection) => 608 | `**Collection [${collection.name}]:** collections://${collection.id}` 609 | ) 610 | ); 611 | } 612 | 613 | // Parent Content 614 | if (content.parent) { 615 | results.push(`**Parent Content:** contents://${content.parent.id}`); 616 | } 617 | 618 | // Child Content(s) 619 | if (content.children) { 620 | results.push( 621 | ...content.children 622 | .filter((child) => child !== null) 623 | .slice(0, 100) 624 | .map((child) => `**Child Content:** contents://${child.id}`) 625 | ); 626 | } 627 | 628 | // Links 629 | if (content.links && content.type === ContentTypes.Page) { 630 | results.push( 631 | ...content.links 632 | .slice(0, 1000) 633 | .map((link) => `**${link.linkType} Link:** ${link.uri}`) 634 | ); 635 | } 636 | 637 | // Observations 638 | if (content.observations) { 639 | results.push( 640 | ...content.observations 641 | .filter((observation) => observation !== null) 642 | .filter((observation) => observation.observable !== null) 643 | .slice(0, 100) 644 | .map( 645 | (observation) => 646 | `**${observation.type}:** ${observation.type.toLowerCase()}s://${observation.observable.id}` 647 | ) 648 | ); 649 | } 650 | 651 | // Content 652 | if (content.pages?.length) { 653 | content.pages.forEach((page) => { 654 | if (page.chunks?.length) { 655 | results.push(`**Page #${(page.index || 0) + 1}:**`); 656 | results.push( 657 | ...(page.chunks 658 | ?.filter((chunk) => chunk?.text) 659 | .map((chunk) => chunk?.text || "") || []) 660 | ); 661 | results.push("\n---\n"); 662 | } 663 | }); 664 | } 665 | 666 | if (content.segments?.length) { 667 | content.segments.forEach((segment) => { 668 | results.push( 669 | `**Transcript Segment [${segment.startTime}-${segment.endTime}]:**` 670 | ); 671 | results.push(segment.text || ""); 672 | results.push("\n---\n"); 673 | }); 674 | } 675 | 676 | if (content.frames?.length) { 677 | content.frames.forEach((frame) => { 678 | results.push(`**Frame #${(frame.index || 0) + 1}:**`); 679 | results.push(frame.text || ""); 680 | results.push("\n---\n"); 681 | }); 682 | } 683 | 684 | if ( 685 | !content.pages?.length && 686 | !content.segments?.length && 687 | !content.frames?.length && 688 | content.markdown 689 | ) { 690 | results.push(content.markdown); 691 | results.push("\n"); 692 | } 693 | 694 | return results.join("\n"); 695 | } 696 | --------------------------------------------------------------------------------