via-stdio.ts 8.9 KB

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