Skip to main content
Glama
server.ts8.18 kB
/** * @fileoverview Glean Model Context Protocol (MCP) Server Implementation * * This server implements the Model Context Protocol, providing a standardized interface * for AI models to interact with Glean's capabilities. It uses stdio * for communication and implements the MCP specification for tool discovery and execution. * * The server exposes four tools: * 1. company_search - Search across Glean's indexed content * 2. people_profile_search - Search for people profiles inside the company * 3. chat - Converse with Glean's AI assistant * 4. read_documents - Retrieve documents by ID or URL */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequest, CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { z } from 'zod'; import * as search from './tools/search.js'; import * as chat from './tools/chat.js'; import * as peopleProfileSearch from './tools/people_profile_search.js'; import * as readDocuments from './tools/read_documents.js'; import { formatGleanError, isGleanError, } from '@gleanwork/mcp-server-utils/errors'; import { VERSION } from './common/version.js'; export const TOOL_NAMES = { companySearch: 'company_search', peopleProfileSearch: 'people_profile_search', chat: 'chat', readDocuments: 'read_documents', }; /** * MCP server instance configured for Glean's implementation. * Supports tool discovery and execution through the MCP protocol. */ const server = new Server( { name: 'Glean Tools MCP', version: VERSION, }, { capabilities: { tools: {} } }, ); /** * Returns the list of available tools with descriptions & JSON Schemas. */ export async function listToolsHandler() { return { tools: [ { name: TOOL_NAMES.companySearch, description: `Find relevant company documents and data Example request: { "query": "What are the company holidays this year?", "datasources": ["drive", "confluence"] } `, inputSchema: z.toJSONSchema(search.ToolSearchSchema), }, { name: TOOL_NAMES.chat, description: `Chat with Glean Assistant using Glean's RAG Example request: { "message": "What are the company holidays this year?", "context": [ "Hello, I need some information about time off.", "I'm planning my vacation for next year." ] } `, inputSchema: z.toJSONSchema(chat.ToolChatSchema), }, { name: TOOL_NAMES.peopleProfileSearch, description: `Search for people profiles in the company Example request: { "query": "Find people named John Doe", "filters": { "department": "Engineering", "city": "San Francisco" }, "pageSize": 10 } `, inputSchema: z.toJSONSchema( peopleProfileSearch.ToolPeopleProfileSearchSchema, ), }, { name: TOOL_NAMES.readDocuments, description: `Read documents from Glean by ID or URL Example request: "documentSpecs": [ { "id": "doc-123", }, { "url": "https://example.com/doc2" } ] `, inputSchema: z.toJSONSchema(readDocuments.ToolReadDocumentsSchema), }, ], }; } /** * Executes a tool based on the MCP callTool request. */ export async function callToolHandler(request: CallToolRequest) { try { if (!request.params.arguments) { throw new Error('Arguments are required'); } switch (request.params.name) { case TOOL_NAMES.companySearch: { const args = search.ToolSearchSchema.parse(request.params.arguments); const result = await search.search(args); const formattedResults = search.formatResponse(result); return { content: [{ type: 'text', text: formattedResults }], isError: false, }; } case TOOL_NAMES.chat: { const args = chat.ToolChatSchema.parse(request.params.arguments); const result = await chat.chat(args); const formattedResults = chat.formatResponse(result); return { content: [{ type: 'text', text: formattedResults }], isError: false, }; } case TOOL_NAMES.peopleProfileSearch: { const args = peopleProfileSearch.ToolPeopleProfileSearchSchema.parse( request.params.arguments, ); const result = await peopleProfileSearch.peopleProfileSearch(args); const formattedResults = peopleProfileSearch.formatResponse(result); return { content: [{ type: 'text', text: formattedResults }], isError: false, }; } case TOOL_NAMES.readDocuments: { const args = readDocuments.ToolReadDocumentsSchema.parse( request.params.arguments, ); const result = await readDocuments.readDocuments(args); const formattedResults = readDocuments.formatResponse(result); return { content: [{ type: 'text', text: formattedResults }], isError: false, }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error) { if (error instanceof z.ZodError) { const errorDetails = error.issues .map((err) => { return `${err.path.join('.')}: ${err.message}`; }) .join('\n'); return { content: [ { type: 'text', text: `Invalid input:\n${errorDetails}`, }, ], isError: true, }; } if (isGleanError(error)) { return { content: [{ type: 'text', text: formatGleanError(error) }], isError: true, }; } return { content: [ { type: 'text', text: `Error: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } server.setRequestHandler(ListToolsRequestSchema, listToolsHandler); server.setRequestHandler(CallToolRequestSchema, callToolHandler); /** * Initializes and starts the MCP server using stdio transport. * This is the main entry point for the server process. * * @async * @param {Object} options - Options for server initialization * @param {string} [options.instance] - The Glean instance name from the command line * @param {string} [options.token] - The Glean API token from the command line * @throws {Error} If server initialization or connection fails */ export async function runServer(options?: { instance?: string; token?: string; }) { // Set environment variables from command line args if provided if (options?.instance) { process.env.GLEAN_INSTANCE = options.instance; } if (options?.token) { process.env.GLEAN_API_TOKEN = options.token; } const transport = new StdioServerTransport(); try { await server.connect(transport); console.error(`Glean MCP Server v${VERSION} running on stdio`); // Create a promise that never resolves to keep the process alive // This is necessary because the MCP server will handle requests // over the transport until terminated return new Promise<void>(() => { // The server keeps running until the process is terminated console.error( 'Server is now handling requests. Press Ctrl+C to terminate.', ); // Handle shutdown signals process.on('SIGINT', () => { console.error('Received SIGINT signal. Shutting down...'); process.exit(0); }); process.on('SIGTERM', () => { console.error('Received SIGTERM signal. Shutting down...'); process.exit(0); }); }); } catch (error) { console.error('Error starting MCP server:', error); throw error; // Re-throw to allow the outer catch to handle it } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gleanwork/mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server