elfiservice/mcp-client-server-sample
If you are the rightful owner of mcp-client-server-sample and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
This project demonstrates how to implement a functional Model Context Protocol (MCP) server using Node.js, Express, and integration with OpenAI.
MCP Client Service Sample
Descrição
Este projeto demonstra como implementar um servidor MCP (Model Context Protocol) funcional usando Node.js, Express e integração com OpenAI. O servidor fornece tools customizados que podem ser chamados por LLMs para executar tarefas específicas.
Funcionalidades
Servidor MCP
- Server MCP padrão com suporte completo ao protocolo
- Tools customizados para busca de clima e notícias
- Prompts pré-definidos para assistente inteligente
- Handlers adequados para todos os request schemas do MCP
Integração HTTP/Express
- Endpoint REST simplificado (
/ask) para frontends - Integração com OpenAI para decisão automática de uso de tools
- Tratamento de erros robusto e mensagens explicativas
Arquitetura
Importações e Dependências
import 'dotenv/config';
import express from "express";
import OpenAI from "openai";
import { Server } from "@modelcontextprotocol/sdk/server";
import { z } from "zod";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
GetPromptRequestSchema,
ListPromptsRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
Estrutura do Servidor MCP
O servidor utiliza a API moderna do SDK MCP v1.17.3+ com:
- Maps para gerenciamento de tools e prompts
- Request handlers específicos para cada tipo de operação
- Schemas tipados para validação de requests
Como Executar
1. Instalar Dependências
npm install
2. Configurar Variáveis de Ambiente (Opcional)
Para usar o endpoint /ask com OpenAI:
export OPENAI_API_KEY="sua-chave-aqui"
Ou criar arquivo .env:
OPENAI_API_KEY=sua-chave-aqui
3. Executar o Servidor
npm start
# ou
node server-mcp.js
4. Testar os Endpoints
Testando sem OpenAI (servidor MCP puro):
O servidor MCP estará disponível para clientes MCP nativos.
Testando com OpenAI (endpoint HTTP):
# Testar busca de clima
curl -X POST http://localhost:3000/ask \
-H "Content-Type: application/json" \
-d '{"text": "Qual o clima em São Paulo?"}'
# Testar busca de notícias
curl -X POST http://localhost:3000/ask \
-H "Content-Type: application/json" \
-d '{"text": "Me dê notícias sobre tecnologia"}'
# Testar sem tool (conversa normal)
curl -X POST http://localhost:3000/ask \
-H "Content-Type: application/json" \
-d '{"text": "Oi, como você está?"}'
Características Técnicas
Compatibilidade MCP
- SDK Version: @modelcontextprotocol/sdk v1.17.3+
- Protocol: Model Context Protocol
- Transport: HTTP/REST (via Express) + MCP nativo
- Capabilities: Tools e Prompts
Integração OpenAI
- Modelo: gpt-4o-mini
- Function Calling: Automático com tools MCP
- Fallback: Resposta direta quando não há tool call
Tratamento de Erros
- Validação de tools inexistentes
- Mensagens de erro explicativas
- Fallback quando OpenAI key não está configurada
Estrutura de Arquivos
├── server-mcp.js # Servidor MCP principal
├── client-mcp.html # Cliente HTML (se existir)
├── package.json # Dependências e configurações
├── .env # Variáveis de ambiente (criar)
├── README.md # Documentação
└── logs/ # Logs do sistema
Dependências Principais
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.17.3",
"express": "^4.18.2",
"openai": "^4.20.1",
"dotenv": "^16.3.1",
"zod": "^3.22.4"
}
}
Observações Importantes
- O servidor MCP funciona independentemente da configuração OpenAI
- A chave OpenAI é necessária apenas para o endpoint simplificado
/ask - Os tools são simulados (retornam dados mock)
- A arquitetura permite fácil extensão com novos tools e prompts
- Compatível com qualquer cliente MCP padrão
Endpoint HTTP para Frontend
POST /ask
Interface simplificada que integra MCP com OpenAI:
app.post("/ask", async (req, res) => {
try {
if (!client) {
return res.status(500).json({
error: "OpenAI API key não configurada. Defina OPENAI_API_KEY como variável de ambiente."
});
}
const userText = req.body.text;
// Converter tools MCP para formato OpenAI
const openaiTools = Array.from(tools.values()).map(tool => ({
type: "function",
function: {
name: tool.name,
description: tool.description,
parameters: tool.inputSchema
}
}));
// Chamada para OpenAI com tools
const decision = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "Use tools do MCP quando necessário." },
{ role: "user", content: userText },
],
tools: openaiTools,
});
// Verificar se LLM solicitou uso de tool
const toolCall = decision.choices[0].message.tool_calls?.[0];
let finalReply = decision.choices[0].message.content || "Não entendi sua solicitação.";
if (toolCall) {
const tool = tools.get(toolCall.function.name);
if (tool) {
const args = JSON.parse(toolCall.function.arguments);
const result = await tool.handler(args);
finalReply = result.content[0].text;
}
}
res.json({ reply: finalReply });
} catch (error) {
console.error("Erro no endpoint /ask:", error);
res.status(500).json({ error: "Erro interno do servidor" });
}
});
Entrada:
{
"text": "Qual o clima em São Paulo?"
}
Saída:
{
"reply": "O clima em São Paulo hoje está ensolarado ☀️"
}
Tools Implementados
1. getWeather
tools.set("getWeather", {
name: "getWeather",
description: "Busca o clima de uma cidade",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "Nome da cidade" }
},
required: ["city"]
},
handler: async ({ city }) => {
return {
content: [{ type: "text", text: `O clima em ${city} hoje está ensolarado ☀️` }],
};
}
});
2. getNews
tools.set("getNews", {
name: "getNews",
description: "Busca notícias sobre um tópico",
inputSchema: {
type: "object",
properties: {
topic: { type: "string", description: "Tópico das notícias" }
},
required: ["topic"]
},
handler: async ({ topic }) => {
return {
content: [{ type: "text", text: `Últimas notícias sobre ${topic}: ...` }],
};
}
});
Prompts Implementados
assistant
prompts.set("assistant", {
name: "assistant",
description: "Você é um assistente simpático que decide quando chamar Tools para responder perguntas.",
handler: async () => {
return {
messages: [
{
role: "system",
content: {
type: "text",
text: "Você é um assistente simpático que decide quando chamar Tools para responder perguntas."
}
}
]
};
}
});
Request Handlers MCP
ListToolsRequestSchema
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: Array.from(tools.values()).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
});
CallToolRequestSchema
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = tools.get(name);
if (!tool) {
throw new Error(`Tool ${name} not found`);
}
return await tool.handler(args);
});
ListPromptsRequestSchema e GetPromptRequestSchema
Handlers similares para gerenciamento de prompts.