Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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.
Vá para o código
Explore o exemplo do servidor TypeScript Remote Model Context Protocol (MCP). Ele demonstra como usar Node.js e 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êineres nos Aplicativos de Contêiner do Azure (ACA). Ele usa um back-end Node.js/TypeScript para fornecer ferramentas ao cliente MCP através do Model Context Protocol. Todas as ferramentas funcionam com um banco de dados SQLite de back-end.
Custo
Para manter os custos baixos, este exemplo usa tipos de preço básicos ou de consumo para a maioria dos recursos. Ajuste o nível conforme necessário e exclua os recursos quando terminar para evitar cobranças.
Pré-requisitos
- Visual Studio Code – versão mais recente para dar suporte ao desenvolvimento do MCP Server.
- GitHub Copilot Extensão do Visual Studio Code
- Extensão do Chat do GitHub Copilot para Visual Studio Code
- CLI do Azure Developer CLI
Um contêiner de desenvolvimento inclui todas as dependências necessárias para este artigo. Você pode executá-lo em Codespaces do GitHub (em um navegador) ou localmente usando o Visual Studio Code.
Para seguir este artigo, verifique se você atende a estes pré-requisitos:
- Uma assinatura do Azure – Criar uma gratuitamente
- Permissões de 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 do Usuário ou Proprietário. Se você não tiver permissões em 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
Abrir o ambiente de desenvolvimento
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 para a Web como a interface. Use os Codespaces do GitHub 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 saber mais, confira Armazenamento e horas por núcleo incluídos mensalmente no GitHub Codespaces.
Siga estas etapas para criar um GitHub Codespace na ramificação main do repositório GitHub Azure-Samples/mcp-container-ts.
Clique com o botão direito do mouse no botão a seguir e selecione Abrir link em uma nova janela. Essa ação permite que você tenha o ambiente de desenvolvimento e a documentação aberta lado a lado.
Na página Criar codespace, analise e selecione Criar novo codespace.
Aguarde até que o codespace seja iniciado. Isso pode levar alguns minutos.
Entre no Azure com o Azure Developer CLI 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ê faz 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 o 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.
- Pule 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 orientam você pelo processo de implantação do servidor MCP de exemplo do Azure.
Publicar no Azure
Importante
Os recursos do Azure nesta seção começam a custar dinheiro imediatamente, mesmo que você interrompa o comando antes que ele seja concluído.
Execute o seguinte comando do Azure Developer CLI para provisionamento de recursos do Azure e implantação de código-fonte:
azd upUse a seguinte tabela para responder aos prompts:
Rápido Resposta Nome do ambiente Mantenha-o curto e em letras minúsculas. Adicione seu nome ou alias. Por exemplo, my-mcp-server. Ele é usado como parte do nome do grupo de recursos.Subscrição Selecione a assinatura para criar os recursos. Localização (para hospedagem) Selecione uma localização perto de você na lista. Local para o modelo do Azure OpenAI Selecione uma localização perto de você na lista. Se o mesmo local estiver disponível como seu primeiro local, selecione-o. Aguarde até que o aplicativo seja implantado. Geralmente, a implantação leva entre 5 e 10 minutos para ser concluída.
Depois que a implantação for concluída, você poderá acessar o servidor MCP usando a URL fornecida na saída. A URL tem esta aparência:
https://<env-name>.<container-id>.<region>.azurecontainerapps.io
- Copie o URL para sua área de transferência. Você precisará dela 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. Ele deve ter esta aparência:"mcp-server-sse-remote": { "type": "sse", "url": "https://<container-id>.<location>.azurecontainerapps.io/sse" }Substitua o valor existente
urlpela URL copiada na etapa anterior.Salve o
mcp.jsonarquivo na.vscodepasta.
Usar ferramentas de servidor TODO MCP no modo de agente
Depois de modificar o servidor MCP, você pode usar as ferramentas que ele fornece no modo de agente. Para usar ferramentas MCP no modo de agente:
Abra a visualização do Chat (
Ctrl+Alt+I) e selecione o modo agente na lista suspensa.Selecione o botão Ferramentas para exibir a lista de ferramentas disponíveis. Opcionalmente, selecione ou desmarque as ferramentas que você deseja usar. Você pode pesquisar ferramentas digitando na caixa de pesquisa.
Insira um prompt como "Preciso enviar um email ao meu gerente na quarta-feira" na caixa de entrada do chat e observe como as ferramentas são automaticamente invocadas conforme necessário, como na seguinte captura de tela:
Observação
Por padrão, quando uma ferramenta é invocada, você precisa confirmar a ação antes da execução da ferramenta. Caso contrário, as ferramentas poderão ser executadas localmente em seu computador e poderão executar ações que modifiquem arquivos ou dados.
Use as opções do menu suspenso do botão Continuar para confirmar automaticamente a ferramenta específica para a sessão atual, o espaço de trabalho ou todas as invocações futuras.
Exploração do código de exemplo
Esta seção fornece uma visão geral dos arquivos de chave e da estrutura de código no exemplo do servidor MCP. O código é organizado em vários componentes principais:
-
index.ts: o ponto de entrada principal para o servidor MCP, que configura o Express.js servidor HTTP e o roteamento. -
server.ts: a camada de transporte que gerencia as conexões de Eventos enviados pelo servidor (SSE) e o manuseio do protocolo MCP. -
tools.ts: contém funções de lógica de negócios e funções utilitárias para o servidor MCP. -
types.ts: define os tipos e interfaces 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 endpoints de Eventos SSE (Server-Sent).
Criar a instância do servidor MCP
O snippet 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 de 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:
SSEPServerencapsula a classe de nívelServerbaixo - Declaração de funcionalidades: o servidor anuncia que dá suporte a ferramentas (mas não a recursos/prompts)
- Convenção de nomenclatura: o nome do servidor se torna parte da identificação do MCP
Configurar rotas expressas
O snippet 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 de extremidade: GET para estabelecer a conexão SSE, POST para enviar mensagens
-
Padrão de delegação: as rotas expressas delegam imediatamente para
SSEPServer
Gerenciamento do ciclo de vida do processo
O trecho de código a seguir manipula o ciclo de vida do servidor, incluindo iniciar o servidor e encerrá-lo de forma adequada em sinais de terminação.
process.on('SIGINT', async () => {
log.error('Shutting down server...');
await server.close();
process.exit(0);
});
Conceitos:
- Encerramento suave: 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, tratando especificamente conexões de eventos Server-Sent (SSE) e roteando mensagens de protocolo MCP.
Configurar uma conexão de cliente SSE e criar um transporte
A SSEPServer classe é a principal camada de transporte para lidar com eventos de Server-Sent (SSE) no servidor MCP. Ele usa a SSEServerTransport classe para gerenciar conexões de cliente individuais. Gerencia vários transportes e o ciclo de vida deles.
export class SSEPServer {
server: Server;
transport: SSEServerTransport | null = null;
transports: Record<string, SSEServerTransport> = {};
constructor(server: Server) {
this.server = server;
this.setupServerRequestHandlers();
}
}
Conceitos:
- Gerenciamento de estado: controla o transporte atual 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 gerada automaticamente armazenada em cache
- Manipuladores de eventos: Limpar ao fechar a conexão
-
Conexão MCP:
server.connect()estabelece a 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 a ID da sessão dos parâmetros de consulta para localizar 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 o
sessionIdparâmetro de consulta para localizar o transporte - Validação da sessão: valida a conexão SSE primeiro.
- Delegação de mensagens: o transporte manipula o processamento real de mensagens
- Respostas de erro: códigos de erro HTTP adequados para sessões ausentes
Instalação do manipulador de protocolo MCP (setupServerRequestHandlers)
O setupServerRequestHandlers método registra os seguintes manipuladores para solicitações de protocolo MCP:
- Um manipulador para
ListToolsRequestSchemaque 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 manipulação de solicitações com segurança de tipo
-
Descoberta de Ferramentas:
ListToolsRequestSchemaretorna matriz TodoTools estática -
Execução da ferramenta:
CallToolRequestSchemalocaliza e executa ferramentas - Tratamento de erros: manipulação elegante de ferramentas desconhecidas
- Formato de resposta: estrutura de resposta compatível com MCP
- Segurança do Tipo: tipos TypeScript garantem a passagem correta do argumento
Lógica de negócios: 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 a 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 "A FAZER" 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 uma representando uma operação TODO específica. No snippet 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 exclusivo para a ferramenta -
description: breve descrição da finalidade da ferramenta -
inputSchema: esquema zod definindo o formato de entrada esperado -
outputSchema: esquema zod definindo o formato de saída esperado -
execute: função que implementa a lógica da ferramenta
Essas definições de ferramenta são importadas server.ts e expostas por meio do manipulador ListToolsRequestSchema.
Conceitos:
- Design de Ferramenta Modular: cada ferramenta é um objeto autocontido
-
Validação de esquema JSON:
inputSchemadefine os parâmetros esperados - Segurança de Tipos: os tipos do TypeScript estão em conformidade com as definições de esquema
- Execução assíncrona: todas as execuções de ferramenta são assíncronas
- Integração de banco de dados: chama funções de banco de dados importadas
- respostasHuman-Readable: retorna cadeias de caracteres formatadas, não dados brutos
Exportação da Matriz de Ferramentas
As ferramentas são exportadas como uma matriz estática, facilitando a importação e o uso 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 em solicitações de cliente.
export const TodoTools = [
{ /* addTodo */ },
{ /* listTodos */ },
{ /* completeTodo */ },
{ /* deleteTodo */ },
{ /* updateTodoText */ },
];
Conceitos:
- Registro Estático: Ferramentas definidas no tempo de carregamento do módulo
- Estrutura de Matriz: a matriz simples facilita a iteração das ferramentas
- Importação/exportação: separação limpa da lógica do servidor
Tratamento de erros de execução da ferramenta
A função de execute de cada ferramenta lida de forma eficaz com os erros e retorna mensagens claras em vez de lançar 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 detectar falhas - Degradação Normal: retorna mensagens de erro descritivas versus lançamento
- User-Friendly Erros: Mensagens adequadas para interpretação pela inteligência artificial
Camada de dados: db.ts
O db.ts arquivo gerencia a conexão de banco de dados SQLite e manipula as operações CRUD para o aplicativo TODO. Ele usa a better-sqlite3 biblioteca para acesso síncrono ao banco de dados.
Inicialização de banco de dados
O banco de dados inicializa conectando-se ao SQLite e criando tabelas se elas não existirem. O snippet 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:
-
banco de dadosIn-Memory:
:memory:significa dados perdidos na reinicialização (somente demonstração/teste) - Modo WAL: registro de Write-Ahead para melhor performance
- Definição de esquema: tabela TODO simples com ID de preenchimento automático
- Tratamento de erros: tratamento normal de falhas de inicialização
- Integração de log: operações de banco de dados são registradas em log para 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);
}
Operação de leitura:
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 exclusã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
- Conversão de Tipos: tipos TypeScript explícitos para resultados da consulta
- Transformação de dados: convertendo inteiros do SQLite em boolianos
- Operações Atômicas: cada função é uma única transação de banco de dados
- Consistência de valor de retorno: metadados de operação de retorno de funções
- Programação Defensiva: padrão de verificação antes da exclusão
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 e classes do utilitário para o servidor.
Registro em log estruturado para depuração e monitoramento: 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 registro de logs e chalk para saída no console com codificação de cores.
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); },
};
};
Gerenciamento de sessão para transportes SSE: helpers/cache.ts
O arquivo helpers/cache.ts usa um Map para armazenar transportes SSE por ID de sessão. Essa abordagem permite que o servidor localize e gerencie rapidamente conexões ativas.
import type { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse";
export const TransportsCache = new Map<string, SSEServerTransport>();
Observação
TransportsCache é um cache em memória simples. Em produção, considere usar uma solução mais robusta, como Redis ou um banco de dados para gerenciamento de sessão.
Resumo do fluxo de execução
O diagrama a seguir ilustra o percurso completo da solicitação do cliente para o servidor MCP e voltar, incluindo execução de ferramentas e operações de banco de dados:
Limpar GitHub Codespaces
Exclua o ambiente de Codespaces do GitHub para maximizar suas horas de núcleo gratuitas.
Importante
Para obter mais informações sobre o armazenamento gratuito da sua conta do GitHub e as horas de núcleo, consulte Armazenamento e horas de núcleo incluídos mensalmente no GitHub Codespaces.
Entre no painel do GitHub Codespaces.
Localize seus Codespaces ativos criados no
Azure-Samples//mcp-container-tsrepositório GitHub.Abra o menu de contexto do codespace e selecione Excluir.
Obter ajuda
Registre seu problema nos problemas do repositório.