via-stdio.ts 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. #!/usr/bin/env bun
  2. // Servidor MCP para gerenciamento de exercícios de academia
  3. import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4. import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5. import {
  6. CallToolRequestSchema,
  7. ListToolsRequestSchema,
  8. } from "@modelcontextprotocol/sdk/types.js";
  9. import { Database } from "bun:sqlite";
  10. import { Exercicio } from "./types";
  11. // Conexão com o Banco de Dados usando o SQLite nativo do Bun
  12. const db = new Database("./academia.sqlite3");
  13. // Criar servidor MCP
  14. const server = new Server(
  15. {
  16. name: "academia-mcp-server",
  17. version: "1.0.0",
  18. },
  19. {
  20. capabilities: {
  21. tools: {},
  22. },
  23. }
  24. );
  25. // Handler para listar todas as ferramentas disponíveis
  26. server.setRequestHandler(ListToolsRequestSchema, async () => {
  27. return {
  28. tools: [
  29. {
  30. name: "buscar_exercicios_por_grupo",
  31. description:
  32. "Busca exercícios filtrando por grupo muscular. Grupos disponíveis: 'Costas (dorsais, lombar)', 'Ombros (deltoides)', 'Pernas', 'Peito (peitoral)', 'Braços (Bíceps, Tríceps, Antebraço)'",
  33. inputSchema: {
  34. type: "object",
  35. properties: {
  36. grupo_muscular: {
  37. type: "string",
  38. description: "Nome do grupo muscular (ex: 'Pernas', 'Peito (peitoral)')",
  39. },
  40. },
  41. required: ["grupo_muscular"],
  42. },
  43. },
  44. {
  45. name: "listar_grupos_musculares",
  46. description: "Lista todos os grupos musculares disponíveis no banco de dados",
  47. inputSchema: {
  48. type: "object",
  49. properties: {},
  50. },
  51. },
  52. {
  53. name: "buscar_exercicio_por_nome",
  54. description: "Busca exercícios específicos por nome (busca parcial, case-insensitive)",
  55. inputSchema: {
  56. type: "object",
  57. properties: {
  58. nome: {
  59. type: "string",
  60. description: "Nome ou parte do nome do exercício (ex: 'agachamento', 'supino')",
  61. },
  62. },
  63. required: ["nome"],
  64. },
  65. },
  66. {
  67. name: "listar_todos_exercicios",
  68. description: "Lista todos os exercícios cadastrados no banco de dados",
  69. inputSchema: {
  70. type: "object",
  71. properties: {},
  72. },
  73. },
  74. {
  75. name: "obter_detalhes_exercicio",
  76. description: "Obtém detalhes completos de um exercício específico pelo ID",
  77. inputSchema: {
  78. type: "object",
  79. properties: {
  80. id: {
  81. type: "number",
  82. description: "ID do exercício",
  83. },
  84. },
  85. required: ["id"],
  86. },
  87. },
  88. ],
  89. };
  90. });
  91. // Handler para executar as ferramentas
  92. // Configuração do servidor MCP de academia
  93. server.setRequestHandler(CallToolRequestSchema, async (request) => {
  94. const { name, arguments: args } = request.params;
  95. try {
  96. switch (name) {
  97. case "buscar_exercicios_por_grupo": {
  98. const { grupo_muscular } = args as { grupo_muscular: string };
  99. const query = db.query<Exercicio, [string]>(
  100. `SELECT id, nome, grupo_muscular, series, repeticoes, intervalo_segundos, observacoes
  101. FROM exercios_vw
  102. WHERE grupo_muscular LIKE ?`
  103. );
  104. const exercicios = query.all(`%${grupo_muscular}%`);
  105. if (exercicios.length === 0) {
  106. return {
  107. content: [
  108. {
  109. type: "text",
  110. text: `Nenhum exercício encontrado para o grupo muscular: ${grupo_muscular}`,
  111. },
  112. ],
  113. };
  114. }
  115. const resultado = exercicios.map(ex =>
  116. `**${ex.nome}**\n` +
  117. `- Séries: ${ex.series}\n` +
  118. `- Repetições: ${ex.repeticoes}\n` +
  119. `- Intervalo: ${ex.intervalo_segundos}s\n` +
  120. `- Observações: ${ex.observacoes}\n`
  121. ).join('\n');
  122. return {
  123. content: [
  124. {
  125. type: "text",
  126. text: `Encontrados ${exercicios.length} exercícios para ${grupo_muscular}:\n\n${resultado}`,
  127. },
  128. ],
  129. };
  130. }
  131. case "listar_grupos_musculares": {
  132. const query = db.query<{ grupo_muscular: string }, []>(
  133. `SELECT DISTINCT grupo_muscular FROM exercios_vw ORDER BY grupo_muscular`
  134. );
  135. const grupos = query.all();
  136. const lista = grupos.map(g => `- ${g.grupo_muscular}`).join('\n');
  137. return {
  138. content: [
  139. {
  140. type: "text",
  141. text: `Grupos musculares disponíveis:\n\n${lista}`,
  142. },
  143. ],
  144. };
  145. }
  146. case "buscar_exercicio_por_nome": {
  147. console.log('Executando ferramenta buscar_exercicio_por_nome');
  148. const { nome } = args as { nome: string };
  149. const query = db.query<Exercicio, [string]>(
  150. `SELECT id, nome, grupo_muscular, series, repeticoes, intervalo_segundos, observacoes
  151. FROM exercios_vw
  152. WHERE nome LIKE ?`
  153. );
  154. const exercicios = query.all(`%${nome}%`);
  155. if (exercicios.length === 0) {
  156. return {
  157. content: [
  158. {
  159. type: "text",
  160. text: `Nenhum exercício encontrado com o nome: ${nome}`,
  161. },
  162. ],
  163. };
  164. }
  165. const resultado = exercicios.map(ex =>
  166. `**ID ${ex.id}: ${ex.nome}**\n` +
  167. `- Grupo: ${ex.grupo_muscular}\n` +
  168. `- Séries: ${ex.series} x ${ex.repeticoes} repetições\n` +
  169. `- Intervalo: ${ex.intervalo_segundos}s\n` +
  170. `- Observações: ${ex.observacoes}\n`
  171. ).join('\n');
  172. return {
  173. content: [
  174. {
  175. type: "text",
  176. text: `Encontrados ${exercicios.length} exercício(s):\n\n${resultado}`,
  177. },
  178. ],
  179. };
  180. }
  181. case "listar_todos_exercicios": {
  182. const query = db.query<Exercicio, []>(
  183. `SELECT id, nome, grupo_muscular, series, repeticoes, intervalo_segundos, observacoes
  184. FROM exercios_vw
  185. ORDER BY grupo_muscular, nome`
  186. );
  187. const exercicios = query.all();
  188. // Agrupa por grupo muscular
  189. const porGrupo: Record<string, Exercicio[]> = {};
  190. exercicios.forEach(ex => {
  191. if (!porGrupo[ex.grupo_muscular]) {
  192. porGrupo[ex.grupo_muscular] = [];
  193. }
  194. porGrupo[ex.grupo_muscular].push(ex);
  195. });
  196. const resultado = Object.entries(porGrupo).map(([grupo, exs]) =>
  197. `### ${grupo}\n` +
  198. exs.map(ex => `- ${ex.nome} (${ex.series}x${ex.repeticoes})`).join('\n')
  199. ).join('\n\n');
  200. return {
  201. content: [
  202. {
  203. type: "text",
  204. text: `Total de ${exercicios.length} exercícios cadastrados:\n\n${resultado}`,
  205. },
  206. ],
  207. };
  208. }
  209. case "obter_detalhes_exercicio": {
  210. const { id } = args as { id: number };
  211. const query = db.query<Exercicio, [number]>(
  212. `SELECT id, nome, grupo_muscular, series, repeticoes, intervalo_segundos, observacoes
  213. FROM exercios_vw
  214. WHERE id = ?`
  215. );
  216. const exercicio = query.get(id);
  217. if (!exercicio) {
  218. return {
  219. content: [
  220. {
  221. type: "text",
  222. text: `Exercício com ID ${id} não encontrado.`,
  223. },
  224. ],
  225. };
  226. }
  227. const detalhes =
  228. `# ${exercicio.nome}\n\n` +
  229. `**Grupo Muscular:** ${exercicio.grupo_muscular}\n` +
  230. `**Séries:** ${exercicio.series}\n` +
  231. `**Repetições:** ${exercicio.repeticoes}\n` +
  232. `**Intervalo:** ${exercicio.intervalo_segundos} segundos\n` +
  233. `**Observações:** ${exercicio.observacoes}`;
  234. return {
  235. content: [
  236. {
  237. type: "text",
  238. text: detalhes,
  239. },
  240. ],
  241. };
  242. }
  243. default:
  244. throw new Error(`Ferramenta desconhecida: ${name}`);
  245. }
  246. } catch (error) {
  247. return {
  248. content: [
  249. {
  250. type: "text",
  251. text: `Erro ao executar ${name}: ${error instanceof Error ? error.message : String(error)}`,
  252. },
  253. ],
  254. isError: true,
  255. };
  256. }
  257. });
  258. // Iniciar servidor MCP de academia
  259. async function main(server: Server) {
  260. // Server transport for stdio: this communicates with a MCP client by reading from the current process' stdin and writing to stdout.
  261. const transport = new StdioServerTransport();
  262. await server.connect(transport);
  263. console.error("Servidor MCP de Academia iniciado via stdio");
  264. }
  265. main(server).catch((error) => {
  266. console.error("Erro fatal:", error);
  267. process.exit(1);
  268. });