Skip to content

Commit adc9a3e

Browse files
feat: Adds people search tool (#89)
1 parent 17ec4b2 commit adc9a3e

File tree

9 files changed

+651
-140
lines changed

9 files changed

+651
-140
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
[![npm version](https://badge.fury.io/js/@gleanwork%2Fmcp-server.svg)](https://badge.fury.io/js/@gleanwork%2Fmcp-server)
66
[![License](https://img.shields.io/npm/l/@gleanwork%2Fmcp-server.svg)](https://github.com/gleanwork/mcp-server/blob/main/LICENSE)
77

8-
A Model Context Protocol (MCP) server implementation for Glean's search and chat capabilities. This server provides a standardized interface for AI models to interact with Glean's content search and conversational AI features.
8+
The Glean MCP Server is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) server that provides seamless integration with Glean's enterprise knowledge.
99

1010
## Features
1111

12-
- **Search Integration**: Access Glean's powerful content search capabilities
13-
- **Chat Interface**: Interact with Glean's AI assistant
12+
- **Company Search**: Access Glean's powerful content search capabilities
13+
- **People Profile Search**: Access Glean's people directory
14+
- **Chat**: Interact with Glean's AI assistant
1415
- **MCP Compliant**: Implements the Model Context Protocol specification
1516

1617
## Tools
@@ -19,13 +20,13 @@ A Model Context Protocol (MCP) server implementation for Glean's search and chat
1920

2021
Search Glean's content index using the Glean Search API. This tool allows you to query Glean's content index with various filtering and configuration options.
2122

22-
For complete parameter details, see [Search API Documentation](https://developers.glean.com/client/operation/search/)
23-
2423
- ### chat
2524

2625
Interact with Glean's AI assistant using the Glean Chat API. This tool allows you to have conversational interactions with Glean's AI, including support for message history, citations, and various configuration options.
2726

28-
For complete parameter details, see [Chat API Documentation](https://developers.glean.com/client/operation/chat/)
27+
- ### people_profile_search
28+
29+
Search Glean's People directory to find employee information.
2930

3031
## Configuration
3132

src/configure.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,9 @@ export async function configure(client: string, options: ConfigureOptions) {
137137
}
138138

139139
// For non-token auth flow (requires GLEAN_OAUTH_ENABLED)
140-
const { instanceOrUrl } = loadCredentials(options);
141-
if (!instanceOrUrl) {
142-
throw new Error('Instance or URL is required for OAuth configuration');
143-
144-
145-
140+
const { instanceOrUrl } = loadCredentials(options);
141+
if (!instanceOrUrl) {
142+
throw new Error('Instance or URL is required for OAuth configuration');
146143
}
147144

148145
// Set environment variables for OAuth flow

src/server.ts

Lines changed: 50 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { z } from 'zod';
2020
import { zodToJsonSchema } from 'zod-to-json-schema';
2121
import * as search from './tools/search.js';
2222
import * as chat from './tools/chat.js';
23+
import * as peopleProfileSearch from './tools/people_profile_search.js';
2324
import {
2425
isGleanError,
2526
GleanError,
@@ -34,6 +35,7 @@ import { VERSION } from './common/version.js';
3435

3536
export const TOOL_NAMES = {
3637
companySearch: 'company_search',
38+
peopleProfileSearch: 'people_profile_search',
3739
chat: 'chat',
3840
};
3941

@@ -50,18 +52,15 @@ const server = new Server(
5052
);
5153

5254
/**
53-
* Handles tool discovery requests by providing a list of available tools.
54-
* Each tool includes its name, description, and input schema in JSON Schema format.
55-
*
56-
* @returns {Promise<{tools: Array<{name: string, description: string, inputSchema: object}>}>}
55+
* Returns the list of available tools with descriptions & JSON Schemas.
5756
*/
58-
server.setRequestHandler(ListToolsRequestSchema, async () => {
57+
export async function listToolsHandler() {
5958
return {
6059
tools: [
6160
{
6261
name: TOOL_NAMES.companySearch,
6362
description: `Find relevant company documents and data
64-
63+
6564
Example request:
6665
6766
{
@@ -87,23 +86,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
8786
`,
8887
inputSchema: zodToJsonSchema(chat.ToolChatSchema),
8988
},
89+
{
90+
name: TOOL_NAMES.peopleProfileSearch,
91+
description: `Search for people profiles in the company
92+
93+
Example request:
94+
95+
{
96+
"query": "Find people named John Doe",
97+
"filters": {
98+
"department": "Engineering",
99+
"city": "San Francisco"
100+
},
101+
"pageSize": 10
102+
}
103+
104+
`,
105+
inputSchema: zodToJsonSchema(
106+
peopleProfileSearch.ToolPeopleProfileSearchSchema,
107+
),
108+
},
90109
],
91110
};
92-
});
111+
}
93112

94113
/**
95-
* Handles tool execution requests by validating input and dispatching to the appropriate tool.
96-
* Supports the following tools:
97-
* - search: Executes a search query against Glean's index
98-
* - chat: Initiates or continues a conversation with Glean's AI
99-
*
100-
* @param {object} request - The tool execution request
101-
* @param {string} request.params.name - The name of the tool to execute
102-
* @param {object} request.params.arguments - The arguments to pass to the tool
103-
* @returns {Promise<{content: Array<{type: string, text: string}>}>}
104-
* @throws {Error} If arguments are missing, tool is unknown, or validation fails
114+
* Executes a tool based on the MCP callTool request.
105115
*/
106-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
116+
export async function callToolHandler(
117+
request: z.infer<typeof CallToolRequestSchema>,
118+
) {
107119
try {
108120
if (!request.params.arguments) {
109121
throw new Error('Arguments are required');
@@ -123,11 +135,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
123135

124136
case TOOL_NAMES.chat: {
125137
const args = chat.ToolChatSchema.parse(request.params.arguments);
126-
const response = await chat.chat(args);
127-
const formattedResponse = chat.formatResponse(response);
138+
const result = await chat.chat(args);
139+
const formattedResults = chat.formatResponse(result);
128140

129141
return {
130-
content: [{ type: 'text', text: formattedResponse }],
142+
content: [{ type: 'text', text: formattedResults }],
143+
isError: false,
144+
};
145+
}
146+
147+
case TOOL_NAMES.peopleProfileSearch: {
148+
const args = peopleProfileSearch.ToolPeopleProfileSearchSchema.parse(
149+
request.params.arguments,
150+
);
151+
const result = await peopleProfileSearch.peopleProfileSearch(args);
152+
const formattedResults = peopleProfileSearch.formatResponse(result);
153+
154+
return {
155+
content: [{ type: 'text', text: formattedResults }],
131156
isError: false,
132157
};
133158
}
@@ -173,7 +198,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
173198
isError: true,
174199
};
175200
}
176-
});
201+
}
202+
203+
server.setRequestHandler(ListToolsRequestSchema, listToolsHandler);
204+
server.setRequestHandler(CallToolRequestSchema, callToolHandler);
177205

178206
/**
179207
* Formats a GleanError into a human-readable error message.

src/test/mocks/handlers.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,67 @@ export const handlers = [
122122
});
123123
},
124124
),
125+
126+
// Handler for people profile search (listentities)
127+
http.post(
128+
'https://:instance-be.glean.com/rest/api/v1/listentities',
129+
async ({ request }) => {
130+
const authHeader = request.headers.get('Authorization');
131+
132+
if (!authHeader || authHeader === 'Bearer invalid_token') {
133+
return new HttpResponse('Invalid Secret\nNot allowed', {
134+
status: 401,
135+
statusText: 'Unauthorized',
136+
headers: {
137+
'Content-Type': 'text/plain; charset=utf-8',
138+
},
139+
});
140+
}
141+
142+
if (authHeader === 'Bearer expired_token') {
143+
return new HttpResponse('Token has expired\nNot allowed', {
144+
status: 401,
145+
statusText: 'Unauthorized',
146+
headers: {
147+
'Content-Type': 'text/plain; charset=utf-8',
148+
},
149+
});
150+
}
151+
152+
if (authHeader === 'Bearer network_error') {
153+
const error = new Error('Network error');
154+
error.name = 'FetchError';
155+
throw error;
156+
}
157+
158+
if (authHeader === 'Bearer server_error') {
159+
return new HttpResponse('Something went wrong', {
160+
status: 500,
161+
statusText: 'Internal Server Error',
162+
headers: {
163+
'Content-Type': 'text/plain; charset=utf-8',
164+
},
165+
});
166+
}
167+
168+
const responseData = {
169+
results: [
170+
{
171+
name: 'Jane Doe',
172+
obfuscatedId: 'abc123',
173+
metadata: {
174+
title: 'Software Engineer',
175+
department: 'Engineering',
176+
location: 'San Francisco',
177+
email: 'jane.doe@example.com',
178+
},
179+
},
180+
],
181+
totalCount: 1,
182+
hasMoreResults: false,
183+
};
184+
185+
return HttpResponse.json(responseData);
186+
},
187+
),
125188
];

0 commit comments

Comments
 (0)