Skip to content

🚀 Add Speaker Fetch Tool to codemotion-mcp-server #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ Para consultar los datos de la agenda he creado un script en Python que utiliza

## Misión 4: Crear un MCP Server para que podamos integrar estas consultas como parte de Github Copilot Chat

Para nuestra cuarta mision he creado un MCP Server que se encuentra en el direction `codemotion-mcp-server`. Este servidor se encarga de recibir las consultasdel usuario, que le llegan a través de GitHub Copilot Chat para buscar en esa base de datos vectorial las charlas que más se ajustan a la consulta del usuario. También se encarga de darle la hora y el día actual para que le de información de las charlas que se están dando en ese momento o que se van a dar en el futuro.


Para nuestra cuarta mision he creado un MCP Server que se encuentra en el direction `codemotion-mcp-server`. Este servidor se encarga de recibir las consultasdel usuario, que le llegan a través de GitHub Copilot Chat para buscar en esa base de datos vectorial las charlas que más se ajustan a la consulta del usuario. También se encarga de darle la hora y el día actual para que le de información de las charlas que se están dando en ese momento o que se van a dar en el futuro.

El servidor proporciona las siguientes herramientas:

- **time**: Obtener la hora actual, opcionalmente con una zona horaria específica
- **sessions**: Buscar sesiones de la Codemotion basadas en una consulta y fecha opcional
- **speakers**: Obtener la lista de todos los ponentes o filtrar según una consulta específica

Puedes probar el servidor usando la herramienta MCP Inspector:

```bash
Expand Down
40 changes: 21 additions & 19 deletions codemotion-mcp-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';;
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "node:crypto";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";


//load env variables
import dotenv from 'dotenv';

// Tools
import { registerTimeTool } from './tools/timeTool.js';
import { registerSessionTool } from './tools/sessionTool.js';


import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';;
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "node:crypto";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";


//load env variables
import dotenv from 'dotenv';

// Tools
import { registerTimeTool } from './tools/timeTool.js';
import { registerSessionTool } from './tools/sessionTool.js';
import { registerSpeakerTool } from './tools/speakerTool.js';


dotenv.config();

/**
Expand Down Expand Up @@ -65,10 +66,11 @@ app.post('/mcp', async (req, res) => {
version: '1.0.0'
});

// Add tools
registerTimeTool(server);
registerSessionTool(server);

// Add tools
registerTimeTool(server);
registerSessionTool(server);
registerSpeakerTool(server);

await server.connect(transport);
} else {
// Invalid request
Expand Down
142 changes: 142 additions & 0 deletions codemotion-mcp-server/src/tools/speakerTool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { z } from 'zod';
import { QdrantClient } from '@qdrant/js-client-rest';
import OpenAI from 'openai';

/**
* Registers the speakers tool in the MCP server.
*
* @param server - The MCP server instance to register the tool with.
*/
export const registerSpeakerTool = (server: any): void => {
// Get speakers from Codemotion agenda in Qdrant
server.tool(
'speakers',
'Get the list of speakers from Codemotion agenda',
{
query: z.string().min(1).max(100).optional()
},
/**
* Handler for fetching speakers from Qdrant based on an optional query.
* @param query - Optional query string to search speakers.
* @returns An object containing the formatted speaker results or an error message.
*/
async ({ query }: { query?: string }) => {
const chalk = (await import('chalk')).default;
console.log(chalk.blue('Codemotion MCP Server: Speakers tool called'));
console.log(chalk.blue('Codemotion MCP Server: Query:', query));

try {
// Create a Qdrant client
const qdrantClient = new QdrantClient({
host: 'qdrant',
port: 6333,
https: false,
checkCompatibility: false
});

let searchResponse;

// If there's a query, use vector search
if (query) {
console.log(chalk.blue('Vectorizing the query...'));

// Create OpenAI client
const openaiClient = new OpenAI({
apiKey: process.env.GITHUB_TOKEN,
baseURL: process.env.GITHUB_MODELS_URL,
});

// Generate the embedding for the query
const response = await openaiClient.embeddings.create({
model: process.env.GITHUB_MODELS_MODEL_FOR_EMBEDDINGS || 'text-embedding-3-large',
input: query
});

// Get the vector from the response
const vector = response.data[0].embedding;
console.log(chalk.blue('Codemotion MCP Server: Vector created'));
console.log(chalk.blue('Codemotion MCP Server: Searching for speakers in Qdrant...'));

// Search for the sessions in Qdrant using vector search
searchResponse = await qdrantClient.query(
process.env.QDRANT_COLLECTION_NAME || 'codemotion',
{
query: vector,
limit: 10,
with_payload: true
}
);
} else {
// If no query provided, get all speakers via scroll API
console.log(chalk.blue('Codemotion MCP Server: No query provided, fetching all speakers...'));

searchResponse = await qdrantClient.scroll(
process.env.QDRANT_COLLECTION_NAME || 'codemotion',
{
limit: 100,
with_payload: true
}
);
}

console.log(chalk.blue('Codemotion MCP Server: Search response received'));

// Extract unique speakers from the search results
const points = query ? searchResponse.points : searchResponse.points;
const speakersSet = new Set<string>();

points.forEach((point: any) => {
// Handle speakers array
if (Array.isArray(point.payload?.speakers)) {
point.payload.speakers.forEach((speaker: string) => {
if (speaker && speaker.trim() !== '') {
speakersSet.add(speaker.trim());
}
});
}
// Handle speakers as string (comma-separated)
else if (typeof point.payload?.speakers === 'string' && point.payload.speakers.trim() !== '') {
point.payload.speakers.split(', ').forEach(speaker => {
if (speaker.trim() !== '') {
speakersSet.add(speaker.trim());
}
});
}
// Handle individual speaker field
else if (typeof point.payload?.speaker === 'string' && point.payload.speaker.trim() !== '') {
speakersSet.add(point.payload.speaker.trim());
}
});

// Convert Set to Array and sort alphabetically
const speakersArray = Array.from(speakersSet).sort();

console.log(chalk.blue(`Codemotion MCP Server: ${speakersArray.length} unique speakers found`));

// Format the result
const speakersList = speakersArray.map(speaker => `- ${speaker}`).join('\n');
const result = `Speakers from Codemotion agenda${query ? ` matching "${query}"` : ''}:\n${speakersList}\n`;

return {
content: [
{
type: 'text',
text: result
}
]
};
} catch (error: any) {
console.dir(error, { depth: null });
console.error(chalk.red('Codemotion MCP Server: Error fetching speakers from Qdrant:', error));
return {
content: [
{
type: 'text',
text: `Error fetching speakers from Qdrant: ${error?.message || 'Unknown error'}`
}
]
};
}
}
);
};