Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Este artigo explica como criar um servidor MCP (Model Context Protocol) usando Node.js e TypeScript. O servidor executa ferramentas e serviços em um ambiente sem servidor. Use essa estrutura como ponto de partida para criar servidores MCP personalizados.
Aceder ao código
Explore o exemplo de servidor MCP (Remote Model Context Protocol) do TypeScript . Ele demonstra como usar o Node.js e o TypeScript para criar um servidor MCP remoto e implantá-lo nos Aplicativos de Contêiner do Azure.
Vá para a seção passo a passo do código para entender como esse exemplo funciona.
Visão geral da arquitetura
O diagrama a seguir mostra a arquitetura simples do aplicativo de exemplo:
O servidor MCP é executado como um aplicativo em contêiner nos Aplicativos de Contêiner do Azure (ACA). Ele utiliza um backend TypeScript Node.jspara fornecer ferramentas ao cliente MCP através do Protocolo de Contexto de Modelo. Todas as ferramentas funcionam com um banco de dados SQLite back-end.
Custo
Para manter os custos baixos, este exemplo usa níveis de preços básicos ou de consumo para a maioria dos recursos. Ajuste a camada conforme necessário e exclua recursos quando terminar para evitar cobranças.
Pré-requisitos
- Visual Studio Code - Versão mais recente para dar suporte ao desenvolvimento do MCP Server.
- Copiloto do GitHub Extensão de código do Visual Studio
- Bate-papo do Copilot do GitHub Extensão de código do Visual Studio
- CLI do desenvolvedor do Azure (azd)
Um contêiner de desenvolvimento inclui todas as dependências necessárias para este artigo. Você pode executá-lo no GitHub Codespaces (em um navegador) ou localmente usando o Visual Studio Code.
Para seguir este artigo, certifique-se de que cumpre estes pré-requisitos:
- Uma subscrição do Azure – Crie uma gratuitamente
- Permissões da conta do Azure – Sua conta do Azure deve ter
Microsoft.Authorization/roleAssignments/writepermissões, como Administrador de Controle de Acesso Baseado em Função, Administrador de Acesso de Usuário ou Proprietário. Se você não tiver permissões no nível de assinatura, deverá receber RBAC para um grupo de recursos existente e implantar nesse grupo.- Sua conta do Azure também precisa de
Microsoft.Resources/deployments/writepermissões no nível da assinatura.
- Sua conta do Azure também precisa de
- Conta do GitHub
Ambiente de desenvolvimento aberto
Siga estas etapas para configurar um ambiente de desenvolvimento pré-configurado com todas as dependências necessárias.
O GitHub Codespaces executa um contêiner de desenvolvimento gerenciado pelo GitHub com o Visual Studio Code for the Web como interface. Use o GitHub Codespaces para a configuração mais simples, pois ele vem com as ferramentas e dependências necessárias pré-instaladas para este artigo.
Importante
Todas as contas do GitHub podem usar Codespaces por até 60 horas gratuitas por mês com duas instâncias principais. Para mais informações, consulte o armazenamento mensal incluído e as horas principais do GitHub Codespaces.
Use as etapas a seguir para criar um novo espaço de código do GitHub na main ramificação do Azure-Samples/mcp-container-ts repositório do GitHub.
Clique com o botão direito do mouse no botão a seguir e selecione Abrir link na nova janela. Esta ação permite que você tenha o ambiente de desenvolvimento e a documentação abertos lado a lado.
Na página Criar espaço de código , revise e selecione Criar novo espaço de código.
Aguarde até que o codespace inicie. Pode demorar alguns minutos.
Entre no Azure com a CLI do Desenvolvedor do Azure no terminal na parte inferior da tela.
azd auth loginCopie o código do terminal e cole-o em um navegador. Siga as instruções para autenticar com sua conta do Azure.
Você executa o restante das tarefas neste contêiner de desenvolvimento.
Observação
Para executar o servidor MCP localmente:
- Configure seu ambiente conforme descrito na seção Configuração do ambiente local no repositório de exemplo.
- Configure seu servidor MCP para usar o ambiente local seguindo as instruções na seção Configurar o servidor MCP no Visual Studio Code no repositório de exemplo.
- Vá para a seção Usar ferramentas de servidor TODO MCP no modo de agente para continuar.
Implantar e executar
O repositório de exemplo contém todos os arquivos de código e configuração para a implantação do Azure do servidor MCP. As etapas a seguir o orientam pelo processo de implantação do Azure do servidor MCP de exemplo.
Publicar no Azure
Importante
Os recursos do Azure nesta seção começam a custar dinheiro imediatamente, mesmo que você pare o comando antes que ele seja concluído.
Execute o seguinte comando da CLI do Desenvolvedor do Azure para provisionamento de recursos do Azure e implantação de código-fonte:
azd upUse a tabela a seguir para responder aos prompts:
Pronta Resposta Nome do ambiente Mantenha-o curto e minúsculo. Adicione o seu nome ou alias. Por exemplo, my-mcp-server. Ele é usado como parte do nome do grupo de recursos.Subscrição Selecione a assinatura na qual criar os recursos. Localização (para hospedagem) Selecione um local perto de você na lista. Local para o modelo OpenAI do Azure Selecione um local perto de você na lista. Se o mesmo local estiver disponível como seu primeiro local, selecione isso. Aguarde até que o aplicativo seja implantado. A implantação geralmente leva entre 5 e 10 minutos para ser concluída.
Quando a implantação estiver concluída, você poderá acessar o servidor MCP usando a URL fornecida na saída. O URL tem esta aparência:
https://<env-name>.<container-id>.<region>.azurecontainerapps.io
- Copie o URL para a área de transferência. Você precisará dele na próxima seção.
Configurar o servidor MCP no Visual Studio Code
Configure o servidor MCP em seu ambiente local do VS Code adicionando a URL ao mcp.json arquivo na .vscode pasta.
Abra o arquivo
mcp.jsonna pasta.vscode.Localize a
mcp-server-sse-remoteseção no arquivo. Deve ter a seguinte aparência:"mcp-server-sse-remote": { "type": "sse", "url": "https://<container-id>.<location>.azurecontainerapps.io/sse" }Substitua o valor existente
urlpelo URL copiado na etapa anterior.Guarde o arquivo
mcp.jsonna pasta.vscode.
Usar ferramentas de servidor TODO MCP no modo de agente
Depois de modificar o servidor MCP, pode-se usar as ferramentas que ele fornece no modo de agente. Para usar ferramentas MCP no modo de agente:
Abra a vista de bate-papo (
Ctrl+Alt+I) e selecione modo de agente na lista suspensa.Selecione o botão Ferramentas para visualizar a lista de ferramentas disponíveis. Opcionalmente, selecione ou desmarque as ferramentas que deseja usar. Você pode pesquisar ferramentas digitando na caixa de pesquisa.
Digite um prompt como "Preciso enviar um e-mail para meu gerente na quarta-feira" na caixa de entrada do chat e observe como as ferramentas são invocadas automaticamente conforme necessário, como na captura de tela a seguir:
Observação
Por padrão, quando uma ferramenta é invocada, você precisa confirmar a ação antes que ela seja executada. Caso contrário, as ferramentas podem ser executadas localmente em sua máquina e podem executar ações que modificam arquivos ou dados.
Use as opções suspensas do botão Continuar para confirmar automaticamente a ferramenta específica para a sessão atual, espaço de trabalho ou todas as invocações futuras.
Explorando o código de exemplo
Esta seção fornece uma visão geral dos arquivos de chave e da estrutura de código no exemplo de servidor MCP. O código está organizado em vários componentes principais:
-
index.ts: O principal ponto de entrada para o servidor MCP, que configura o Express.js servidor HTTP e roteamento. -
server.ts: A camada de transporte que faz a gestão das ligações de eventos Server-Sent (SSE) e da manipulação do protocolo MCP. -
tools.ts: Contém lógica de negócios e funções de utilidade para o servidor MCP. -
types.ts: Define os tipos e interfaces do TypeScript usados em todo o servidor MCP.
index.ts - Como o servidor inicia e aceita conexões HTTP
O index.ts arquivo é o principal ponto de entrada para o servidor MCP. Ele inicializa o servidor, configura o servidor HTTP Express.js e define o roteamento para os pontos finais de eventos de Server-Sent (SSE).
Criar a instância do servidor MCP
O trecho de código a seguir inicializa o servidor MCP usando a StreamableHTTPServer classe, que é um wrapper em torno da classe MCP Server principal. Essa classe manipula a camada de transporte para eventos Server-Sent (SSE) e gerencia conexões de cliente.
const server = new StreamableHTTPServer(
new Server(
{
name: 'todo-http-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
)
);
Conceitos:
-
Padrão de composição:
SSEPServerenvolve a classe de baixo nívelServer - Declaração de capacidades: O servidor anuncia que suporta ferramentas (mas não recursos/prompts)
- Convenção de nomenclatura: O nome do servidor torna-se parte da identificação MCP
Configurar rotas Express
O trecho de código a seguir configura o servidor Express.js para lidar com solicitações HTTP de entrada para conexões SSE e tratamento de mensagens:
router.post('/messages', async (req: Request, res: Response) => {
await server.handlePostRequest(req, res);
});
router.get('/sse', async (req: Request, res: Response) => {
await server.handleGetRequest(req, res);
});
Conceitos:
- Padrão de dois pontos finais: GET para estabelecer conexão SSE, POST para enviar mensagens
-
Padrão de delegação: Rotas expressas delegadas imediatamente para
SSEPServer
Gestão do ciclo de vida do processo
O trecho de código a seguir lida com o ciclo de vida do servidor, incluindo iniciar o servidor e desligá-lo normalmente em sinais de terminação:
process.on('SIGINT', async () => {
log.error('Shutting down server...');
await server.close();
process.exit(0);
});
Conceitos:
- Encerramento controlado: limpeza adequada ao pressionar Ctrl+C
- Limpeza assíncrona: a operação de fechamento do servidor é assíncrona
- Gerenciamento de recursos: importante para conexões SSE
Camada de transporte: server.ts
O server.ts arquivo implementa a camada de transporte para o servidor MCP, especificamente manipulando conexões de eventos de Server-Sent (SSE) e roteando mensagens do protocolo MCP.
Configurar uma conexão de cliente SSE e criar um transporte
A SSEPServer classe é a camada de transporte principal para manipular eventos de Server-Sent (SSE) no servidor MCP. Ele usa a SSEServerTransport classe para gerenciar conexões de cliente individuais. Gere múltiplos transportes e o seu ciclo de vida.
export class SSEPServer {
server: Server;
transport: SSEServerTransport | null = null;
transports: Record<string, SSEServerTransport> = {};
constructor(server: Server) {
this.server = server;
this.setupServerRequestHandlers();
}
}
Conceitos:
- Gestão do Estado: Rastreia os transportes atuais e todos os transportes
-
Mapeamento de sessão:
transportso objeto mapeia IDs de sessão para instâncias de transporte - Delegação do construtor: configura imediatamente os manipuladores de solicitação
Estabelecimento de conexão SSE (handleGetRequest)
O handleGetRequest método é responsável por estabelecer uma nova conexão SSE quando um cliente faz uma solicitação GET para o /sse ponto de extremidade.
async handleGetRequest(req: Request, res: Response) {
log.info(`GET ${req.originalUrl} (${req.ip})`);
try {
log.info("Connecting transport to server...");
this.transport = new SSEServerTransport("/messages", res);
TransportsCache.set(this.transport.sessionId, this.transport);
res.on("close", () => {
if (this.transport) {
TransportsCache.delete(this.transport.sessionId);
}
});
await this.server.connect(this.transport);
log.success("Transport connected. Handling request...");
} catch (error) {
// Error handling...
}
}
Conceitos:
-
Criação de transporte: Novo
SSEServerTransportpara cada solicitação GET - Gerenciamento de sessão: ID de sessão gerado automaticamente armazenado em cache
- Manipuladores de eventos: Limpeza no encerramento da conexão
-
Conexão MCP:
server.connect()estabelece conexão de protocolo - Fluxo assíncrono: a configuração da conexão é assíncrona com limites de erro
Processamento de mensagens (handlePostRequest)
O handlePostRequest método processa solicitações POST de entrada para lidar com mensagens MCP enviadas pelo cliente. Ele usa o ID de sessão dos parâmetros de consulta para encontrar a instância de transporte correta.
async handlePostRequest(req: Request, res: Response) {
log.info(`POST ${req.originalUrl} (${req.ip}) - payload:`, req.body);
const sessionId = req.query.sessionId as string;
const transport = TransportsCache.get(sessionId);
if (transport) {
await transport.handlePostMessage(req, res, req.body);
} else {
log.error("Transport not initialized. Cannot handle POST request.");
res.status(400).json(/* error response */);
}
}
Conceitos:
-
Pesquisa de sessão: usa
sessionIdo parâmetro de consulta para localizar o transporte - Validação de sessão: Valida primeiro a conexão SSE.
- Delegação de mensagens: o transporte é responsável pelo processamento efetivo de mensagens
- Respostas de erro: códigos de erro HTTP adequados para sessões ausentes
Configuração do manipulador de protocolo MCP (setupServerRequestHandlers)
O setupServerRequestHandlers método registra os seguintes manipuladores para solicitações de protocolo MCP:
- Um manipulador para
ListToolsRequestSchema, que retorna a lista de ferramentas TODO disponíveis. - Um manipulador para
CallToolRequestSchemaque localiza e executa a ferramenta solicitada com os argumentos fornecidos.
Esse método usa esquemas Zod para definir os formatos de solicitação e resposta esperados.
private setupServerRequestHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async (_request) => {
return {
tools: TodoTools,
};
});
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = TodoTools.find((tool) => tool.name === name);
if (!tool) {
return this.createJSONErrorResponse(`Tool "${name}" not found.`);
}
const response = await tool.execute(args as any);
return { content: [{ type: "text", text: response }] };
});
}
Conceitos:
- Schema-Based Roteamento: Utiliza esquemas Zod para tratamento de solicitações de tipo seguro
-
Descoberta de ferramentas:
ListToolsRequestSchemaretorna a matriz estática TodoTools -
Execução de ferramentas:
CallToolRequestSchemalocaliza e executa ferramentas - Tratamento de erros: manipulação graciosa de ferramentas desconhecidas
- Formato de resposta: estrutura de resposta compatível com MCP
- Segurança do tipo: os tipos TypeScript garantem a passagem correta do argumento
Lógica de negócio: tools.ts
O tools.ts arquivo define a funcionalidade real disponível para clientes MCP:
- Metadados da ferramenta (nome, descrição, esquemas)
- Esquemas de validação de entrada
- Lógica de execução da ferramenta
- Integração com camada de banco de dados
Este Servidor MCP define quatro ferramentas de gerenciamento TODO:
-
add_todo: Cria um novo item TODO -
complete_todo: Marca um item TODO como concluído -
delete_todo: Exclui um item TODO -
list_todos: Lista todos os itens TODO -
update_todo_text: Atualiza o texto de um item TODO existente
Padrão de definição de ferramenta
As ferramentas são definidas como uma matriz de objetos, cada um representando uma operação TODO específica. No trecho de código a seguir, a addTodo ferramenta é definida:
{
name: "addTodo",
description: "Add a new TODO item to the list...",
inputSchema: {
type: "object",
properties: {
text: { type: "string" },
},
required: ["text"],
},
outputSchema: { type: "string" },
async execute({ text }: { text: string }) {
const info = await addTodo(text);
return `Added TODO: ${text} (id: ${info.lastInsertRowid})`;
},
}
Cada definição de ferramenta tem:
-
name: Identificador único da ferramenta -
description: Breve descrição da finalidade da ferramenta -
inputSchema: Esquema Zod definindo o formato de entrada esperado -
outputSchema: Zod esquema definindo o formato de saída esperado -
execute: Função que implementa a lógica da ferramenta
Essas definições de ferramentas são importadas em server.ts e expostas através do manipulador ListToolsRequestSchema.
Conceitos:
- Design de ferramenta modular: Cada ferramenta é um objeto autônomo
-
Validação de esquema JSON:
inputSchemadefine parâmetros esperados - Segurança de tipo: os tipos TypeScript correspondem às definições de esquema
- Execução assíncrona: Todas as execuções de ferramentas são assíncronas
- Integração de banco de dados: chama funções de banco de dados importadas
- Human-Readable Respostas: retorna cadeias de caracteres formatadas, não dados brutos
Exportação de conjunto de ferramentas
As ferramentas são exportadas como uma matriz estática, tornando-as fáceis de importar e usar no servidor. Cada ferramenta é um objeto com seus metadados e lógica de execução. Essa estrutura permite que o servidor MCP descubra e execute dinamicamente ferramentas com base nas solicitações do cliente.
export const TodoTools = [
{ /* addTodo */ },
{ /* listTodos */ },
{ /* completeTodo */ },
{ /* deleteTodo */ },
{ /* updateTodoText */ },
];
Conceitos:
- Registro estático: Ferramentas definidas no tempo de carregamento do módulo
- Estrutura da matriz: A matriz simples torna as ferramentas fáceis de iterar
- Importar/Exportar: Separação limpa da lógica do servidor
Tratamento de erros de execução da ferramenta
A função de cada ferramenta execute lida eficientemente com erros e retorna mensagens claras em vez de gerar exceções. Essa abordagem garante que o servidor MCP forneça uma experiência de usuário perfeita.
As ferramentas lidam com vários cenários de erro:
async execute({ id }: { id: number }) {
const info = await completeTodo(id);
if (info.changes === 0) {
return `TODO with id ${id} not found.`;
}
return `Marked TODO ${id} as completed.`;
}
Conceitos:
-
Verificação de resposta do banco de dados: usa
info.changespara detetar falhas - Degradação graciosa: retorna mensagens de erro descritivas vs lançamento
- User-Friendly Erros: Mensagens adequadas para interpretação por IA
Camada de dados: db.ts
O db.ts arquivo gerencia a conexão de banco de dados SQLite e manipula operações CRUD para o aplicativo TODO. Ele usa a biblioteca para acesso síncrono ao better-sqlite3 banco de dados.
Inicialização do banco de dados
O banco de dados é inicializado conectando-se ao SQLite e criando tabelas se elas não existirem. O trecho de código a seguir mostra o processo de inicialização:
const db = new Database(":memory:", {
verbose: log.info,
});
try {
db.pragma("journal_mode = WAL");
db.prepare(
`CREATE TABLE IF NOT EXISTS ${DB_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
completed INTEGER NOT NULL DEFAULT 0
)`
).run();
log.success(`Database "${DB_NAME}" initialized.`);
} catch (error) {
log.error(`Error initializing database "${DB_NAME}":`, { error });
}
Conceitos:
-
In-Memory Banco de dados:
:memory:significa dados perdidos na reinicialização (somente demonstração/teste) - Modo WAL: Write-Ahead registo de eventos para melhor desempenho
- Definição de esquema: tabela simples TODO com ID auto-incrementado
- Tratamento de erros: Tratamento normal de falhas de inicialização
- Integração de registo: as operações na base de dados são registadas para fins de depuração
Padrões de operação CRUD
O db.ts arquivo fornece quatro operações CRUD principais para gerenciar itens TODO:
Criar operação:
export async function addTodo(text: string) {
log.info(`Adding TODO: ${text}`);
const stmt = db.prepare(`INSERT INTO todos (text, completed) VALUES (?, 0)`);
return stmt.run(text);
}
Leia a operação:
export async function listTodos() {
log.info("Listing all TODOs...");
const todos = db.prepare(`SELECT id, text, completed FROM todos`).all() as Array<{
id: number;
text: string;
completed: number;
}>;
return todos.map(todo => ({
...todo,
completed: Boolean(todo.completed),
}));
}
Operação de atualização:
export async function completeTodo(id: number) {
log.info(`Completing TODO with ID: ${id}`);
const stmt = db.prepare(`UPDATE todos SET completed = 1 WHERE id = ?`);
return stmt.run(id);
}
Operação de eliminação:
export async function deleteTodo(id: number) {
log.info(`Deleting TODO with ID: ${id}`);
const row = db.prepare(`SELECT text FROM todos WHERE id = ?`).get(id) as
| { text: string }
| undefined;
if (!row) {
log.error(`TODO with ID ${id} not found`);
return null;
}
db.prepare(`DELETE FROM todos WHERE id = ?`).run(id);
log.success(`TODO with ID ${id} deleted`);
return row;
}
Conceitos:
- Instruções preparadas: Proteção contra injeção de SQL
- Type Casting: tipos TypeScript explícitos para resultados de consulta
- Transformação de dados: convertendo inteiros SQLite em booleanos
- Operações atômicas: Cada função é uma única transação de banco de dados
- Consistência do valor de retorno: as funções retornam metadados da operação
- Programação defensiva: padrão de verificação antes de excluir
Design de esquema
O esquema de banco de dados é definido no db.ts arquivo usando uma instrução SQL simples. A todos tabela tem três campos:
CREATE TABLE todos (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- Unique identifier
text TEXT NOT NULL, -- TODO description
completed INTEGER NOT NULL DEFAULT 0 -- Boolean as integer
);
Utilitários auxiliares: helpers/ diretório
O helpers/ diretório fornece funções de utilitário e classes para o servidor.
Log estruturado para debugging e monitorização: helpers/logs.ts
O helpers/logs.ts arquivo fornece um utilitário de log estruturado para o servidor MCP. Ele usa a biblioteca debug para registo de logs e chalk para saída com códigos de cores no console.
export const logger = (namespace: string) => {
const dbg = debug('mcp:' + namespace);
const log = (colorize: ChalkInstance, ...args: any[]) => {
const timestamp = new Date().toISOString();
const formattedArgs = [timestamp, ...args].map((arg) => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2);
}
return arg;
});
dbg(colorize(formattedArgs.join(' ')));
};
return {
info(...args: any[]) { log(chalk.cyan, ...args); },
success(...args: any[]) { log(chalk.green, ...args); },
warn(...args: any[]) { log(chalk.yellow, ...args); },
error(...args: any[]) { log(chalk.red, ...args); },
};
};
Gestão de sessões para transportes SSE: helpers/cache.ts
O helpers/cache.ts arquivo usa um Map para armazenar transportes SSE por ID de sessão. Essa abordagem permite que o servidor encontre e gerencie rapidamente conexões ativas.
import type { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse";
export const TransportsCache = new Map<string, SSEServerTransport>();
Observação
O TransportsCache é um cache simples na memória. Na produção, considere o uso de uma solução mais robusta como o Redis ou um banco de dados para gerenciamento de sessão.
Resumo do fluxo de execução
O diagrama a seguir ilustra a jornada completa da solicitação do cliente para o servidor MCP e vice-versa, incluindo a execução da ferramenta e as operações do banco de dados:
Limpar espaços de código do GitHub
Exclua o ambiente do GitHub Codespaces para maximizar suas horas gratuitas por núcleo.
Importante
Para obter mais informações sobre o armazenamento gratuito e as horas principais da sua conta do GitHub, consulte Armazenamento mensal incluído e horas principais do GitHub Codespaces.
Faça login no painel do GitHub Codespaces.
Encontre seus Codespaces ativos criados a partir do
Azure-Samples//mcp-container-tsrepositório GitHub.Abra o menu de contexto do espaço de código e selecione Excluir.
Obter ajuda
Registre seu problema nos problemas do repositório.