<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jean Carlo Vittory Laguna</title>
    <description>The latest articles on DEV Community by Jean Carlo Vittory Laguna (@jeanvittory).</description>
    <link>https://dev.to/jeanvittory</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1056887%2F722c61c9-e6a1-4f09-a64c-9370e477a780.jpeg</url>
      <title>DEV Community: Jean Carlo Vittory Laguna</title>
      <link>https://dev.to/jeanvittory</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeanvittory"/>
    <language>en</language>
    <item>
      <title>Embeddings y RAG en aplicaciones web</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Sat, 08 Nov 2025 04:19:21 +0000</pubDate>
      <link>https://dev.to/jeanvittory/embeddings-y-rag-en-aplicaciones-web-5dnm</link>
      <guid>https://dev.to/jeanvittory/embeddings-y-rag-en-aplicaciones-web-5dnm</guid>
      <description>&lt;p&gt;Quiero esta vez escribir un poco sobre el proceso de construcción de una pequeña idea que use para poder comprender el uso de embeddings y RAG en aplicaciones web que requieran este tipo de tecnologías. &lt;/p&gt;

&lt;p&gt;Buscando entender un poco estos conceptos encontré la idea de poder ayudar a mejorar la experiencia de usuarios que necesiten llevar al siguiente nivel sus procesos de capacitación o uso de procesos sistemáticos que se encuentren alojados en archivos PDF o word a través de una plataforma que ponga a disposición un chatbot que contenga toda la información de estos archivos y el usuario pueda realizar preguntas sobre ellos de forma natural y logre recibir una respuesta. &lt;/p&gt;

&lt;p&gt;Segun esta necesidad encontre que los embeddings y el uso de RAG en chatbots con IA logran no solo cubrir esta necesidad sino abrir un abanico de opciones para poder llevar esta idea a niveles superiores. &lt;/p&gt;

&lt;p&gt;En un primer momento el flujo fue muy sencillo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5ewez3r6q7w89z37ep4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5ewez3r6q7w89z37ep4.png" alt="first diagram" width="800" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la anterior imágen encontramos &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 nodos que representan a openai&lt;/li&gt;
&lt;li&gt;2 nodos que representan endpoints de nuestra aplicación&lt;/li&gt;
&lt;li&gt;2 nodos que representa nuestra base de datos vectorial&lt;/li&gt;
&lt;li&gt;1 nodo que representa una base de datos tipo postgreSQL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ahora bien, lo primero que debemos resaltar son los nodos de openai ya que estos nos permiten generar los embeddings y además de esto la respuesta final que le entregaremos al usuario. &lt;/p&gt;

&lt;p&gt;Los nodos que representan la base de datos vectorial nos permiten almacenar estos datos vectorizados, en este caso estamos usando postgreSQL, para poder consultarlos y de esta forma alimentar a openai con esta informacion y lograr dar con una respuesta concreta al usuario. &lt;/p&gt;

&lt;p&gt;El nodo de postgreSQL nos permite almacenar datos del documento almacenado para poder mostrar al usuario en una UI el listado de documentos en el sistema. &lt;/p&gt;

&lt;p&gt;Por último los 2 nodos que representan los endpoints son las puertas de entrada y salida para poder consumir todo estos recursos desde nuestra aplicación. &lt;/p&gt;

&lt;p&gt;Paremos un momento e intentemos revisar detalladamente el papel de los 3 nodos de openai. Estos, como ya lo nombramos, cumplen dos funciones: Realizar el proceso de creación del embedding y generar la respuesta final del usuario a través del chat. Pero ¿Qué es un embedding y porque debemos de utilizar esta técnica para lograr nuestro objetivo?&lt;/p&gt;

&lt;p&gt;Un embedding en pocas palabras es la representación numérica de cada palabra de nuestro elemento a almacenar. Esta representación no es arbitraria ya que, usando matematica de vectores, logra llegar a una representación numerica de cada palabra.&lt;/p&gt;

&lt;p&gt;Para lograr esto, empresas como Openai, Anthropic o Google nos proveen de modelos especializados en embeddings. Muy similar a los modelos que siempre escuchamos como GPT-4o, Sonnet o Gemini existen modelos para embeddings. En este caso estaremos utilizando el modelo &lt;code&gt;text-embedding-3-small&lt;/code&gt; de openai sin embargo existen muchos otros y su uso dependera del objetivo de nuestra aplicación y el presupuesto que tengamos a disposición. &lt;/p&gt;

&lt;p&gt;Por ejemplo &lt;code&gt;text-embedding-3-small&lt;/code&gt; es un modelo de uso general y economico con 1536 valores por vector pero si vamos a modelos como &lt;code&gt;text-embedding-3-large&lt;/code&gt; encontramos un modelo mas preciso con 3072 valores por vector pero en ese mismo sentido el procesamiento es mas costoso. &lt;/p&gt;

&lt;p&gt;Ademas de esto debemos tener en cuenta factores como el dominio de entrenamiento del modelo, soporte en varios idiomas o capacidad semantica. &lt;/p&gt;

&lt;p&gt;Una vez acalarado esto debemos almacenar nuestros vectores en una base de datos que nos permita almacenar este tipo de datos y para ello usamos PostgreSQL a través de su extensión &lt;code&gt;pgvector&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create extension if not exists vector;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Sin embargo, tenemos bases de datos que soportan nativamente vectores como es el caso de &lt;a href="https://www.pinecone.io/" rel="noopener noreferrer"&gt;Pinecone&lt;/a&gt; o &lt;a href="https://www.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;. Para este caso decidimos usar PostgreSQL porque estamos utilizando Supabase como BaaS y así proveernos de sus diferentes features para un desarrollo más rápido. &lt;/p&gt;

&lt;p&gt;Dentro de esta tabla de Postgres, utilizando nuestra extensión de pgvector, ya podemos generar columnas que nos almacenen este nuevo tipo de dato. Por ejemplo, para nuestro caso tenemos nuestra tabla definida así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create table if not exists document_sections (
  id               bigserial primary key,
  document_id      bigint not null references documents(id) on delete cascade,
  section_order    int not null,                      
  section_content  text not null,                     
  embedding        vector(1536) not null,            
  meta             jsonb not null default '{}'::jsonb,
  created_at       timestamptz not null default now()
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como puedes observar nuestro embedding se almacenará aquí:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;embedding vector(1536) not null&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;En donde el número 1536 representa la cantidad de elementos que existirán dentro de cada vector. &lt;/p&gt;

&lt;p&gt;Vale, ahora debemos de hablar de algo muy importante en este punto y es algo llamado &lt;code&gt;chunk&lt;/code&gt;. Tanto nuestro modelo de embedding como nuestra tabla de vectores no almacena todo el documento de golpe ya que si el usuario llega a cargar un documento muy grande la exigencia de computo sería enorme, la precision a la hora de realizar busquedas se veria afectada y, además de esto, debemos de tener en cuenta que estos modelos de embeddings tambien tienen un máximo de tokens por lo tanto no podemos procesar tokens de manera infinita. &lt;/p&gt;

&lt;p&gt;Para ello nos vemos en la necesidad de procesar los datos por chunks. Para lograr esto tenemos múltiples caminos. Implementar nuestro propio "chunker" o utilizar uno de alguna libreria. Para este caso decidí crear mi propio "chunker" ya que no es un producto con pretensiones comerciales pero facilmente puedes utilizar &lt;a href="https://www.npmjs.com/package/sentence-splitter?activeTab=dependencies" rel="noopener noreferrer"&gt;sentence-splitter&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;La tarea de realizar chunks es muy importante ya que de esta depende el nivel de precision que existira en los vectores. Debes de tener en cuenta que realizar cortes muy grandes genera distorsion y sesgo a la hora de realizar busquedas vectoriales pero si realizamos cortes muy pequeños perdemos contexto global de nuestro texto, responder a preguntas generales se vuelve complejo y debemos de procesar más embeddings lo cual nos genera mas costos.&lt;/p&gt;

&lt;p&gt;Vale, entonces hagamos un paso a paso del funcionamiento de nuestra aplicación. En esta primera parte describiremos el proceso de uploading:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El usuario hace un upload del documento desde la UI&lt;/li&gt;
&lt;li&gt;Se envían estos datos al servidor usando el endpoint send-file&lt;/li&gt;
&lt;li&gt;Una vez dentro de send-file ejecutamos el chunk de nuestro archivo  y por cada chunk generamos un embedding.&lt;/li&gt;
&lt;li&gt;Insertamos nuestro embedding en la base de datos vectorial. &lt;/li&gt;
&lt;li&gt;Insertamos en nuestra tabla convencional de postgres los datos del archivo para mostrar en la UI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;En este punto ya con nuestro archivo en nuestra base de datos podemos consultarlo y para ello recurrimos a los siguientes pasos&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;El usuario realiza una pregunta en el chat&lt;/li&gt;
&lt;li&gt;Esta pregunta la transformamos en un embedding &lt;/li&gt;
&lt;li&gt;Usamos este embedding generado y realizamos un busqueda vectorial en nuestra base de datos vectorial con el fin de encontrar similitudes entre el embedding generado por la pregunta y los embeddings almacenados en nuestra base de datos&lt;/li&gt;
&lt;li&gt;Usamos un LLM para procesar tanto la respuesta de la busqueda vectorial utilizada aqui como contexto y la pregunta a responder enviada inicialmente por el usuario.&lt;/li&gt;
&lt;li&gt;La respuesta generada la devolvemos al cliente. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;De esta forma cerramos el ciclo de nuestra aplicación y como puedes ver estamos utilizando un modelo de LLM alimentando con los datos de nuestra base de datos vectorial para generar una respuesta que se limite solamente a estos datos. En nuestra aplicación esta última sección luce algo así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    const aiResponse = await openai.chat.completions.create({
      model: "gpt-4o-mini",
      temperature: 1,
      max_completion_tokens: 150,
      messages: [
        {
          role: "system",
          content:
            "Use the context if it exists. If the context contains 'NO_COINCIDENCE', thank the user and kindly explain that you cannot respond with the information available.",
        },
        {
          role: "user",
          content: `
                Using the following information please answer the question: 
                Context: ${context}
                Question: ${message}
          `,
        },
      ],
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Habras notado que utilizamos un termino &lt;code&gt;busqueda vectorial&lt;/code&gt; al cual no nos hemos referido hasta el momento. Existen 3 tipos de busquedas vectoriales que podemos llegar a aplicar: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Similitud del coseno (Cosine similarity)&lt;/li&gt;
&lt;li&gt;Producto interno (Dot product)&lt;/li&gt;
&lt;li&gt;Distancia euclidiana (Euclidian distance)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cada una de estas tres opciones tiene su objetivo, para nuestro caso aplicamos la busqueda por similitud del coseno. Este algoritmo nos permite realizar busquedas por angulo entre dos vectores e ignora la longitud de los mismos. En otras palabras dentro del plano vectorial si el angulo entre dos vectores es pequeño representa similitud, por el contrario si es mayor o totalmente opuesto representan menos similitud o directamente significados totalmente opuestos. &lt;/p&gt;

&lt;p&gt;Por lo general en embeddings se tiende a utilizar la similitud por coseno ya que nos interesa mas la direccion semantica (angulo) más que la longitud del vector ya que este algoritmo normaliza esta longitud por defecto haciendo que este valor pierda importancia.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Se debe de tener en cuenta que esta normalización tiene un costo y, si bien es el algoritmo más utilizado, el producto interno tiene un mayor rendimiento pero la longitud al no estar normalizada puede llegar a introducir sesgos o alucinaciones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En nuestro caso hemos diseñado esta consulta a traves de un procedimiento almacenado en supabase utilizando la similitud del coseno.&lt;/p&gt;

&lt;p&gt;Una vez repasado de manera somera el flujo de nuestra aplicación podemos empezar a ver que factores extra nos podemos llegar a ecnontrar y como los podemos resolver. &lt;/p&gt;

&lt;p&gt;Uno de los principales inconvenientes que podemos llegar a enfrentar  es no bloquear al usuario durante todo este poceso ya que, como puedes llegar a imaginar, el tiempo que trascurre entre subir el archivo y generar los embeddings puede tomar un tiempo dependiendo de la velocidad de red que haya en ese momento y la disponibilidad del servicio de openai. &lt;/p&gt;

&lt;p&gt;En ese sentido, para no bloquear al usuario podemos generar un sistema de colas que nos permitan desacoplar este proceso y, a través de un estado de carga, avisar al usuario cuando el archivo ya se encuentra listo para su consulta. &lt;/p&gt;

&lt;p&gt;Para lograr esto podemos utilizar servicios como SQS de AWS o algun broker de mensajeria como RabbitMQ. Ya depende de tus necesidades y presupuesto. &lt;/p&gt;

&lt;p&gt;Para nuestro caso hemos decidido mantenerlo simple y usamos un servicio de mensajeria de &lt;a href="https://upstash.com/" rel="noopener noreferrer"&gt;Upstash&lt;/a&gt; llamado &lt;code&gt;Qstash&lt;/code&gt; lo cual nos permitio desacoplar nuestro sistema sin necesidad de crear una infraestrcutura manteniendo simple nuestra implementación. &lt;/p&gt;

&lt;p&gt;Otro posible problema que puedes llegar a enfrentar es que si trabajas con servicios serverless como Lambdas o endpoints de Next estos tienen un limite de datos que se pueden llegar a transmitir en cada petición, por lo tanto enviar archivos PDF o docx puede generarte problemas de tipo &lt;code&gt;HTTP 413 "Content Too Large"&lt;/code&gt;. Para solucionar esto puedes usar servicios como S3 de AWS o Google Storage en GCP. Lo que implementamos nosotros fue: Desde el cliente subir el archivo a un bucket en supabase con un &lt;code&gt;storagePath&lt;/code&gt; y, en el punto en el que necesitamos generar el embedding ya del lado del server, consultamos este bucket, descargamos el archivo y generamos el embedding y asi no tenemos que recurrir a envios de red muy grandes que pueden afectar el rendimiento de nuestra aplicación. &lt;/p&gt;

&lt;p&gt;Por último te dejo el diagrama final de la aplicacion en donde se detalla la interacción de cada componente: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzf9pzk09p4gd4e11bsiu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzf9pzk09p4gd4e11bsiu.png" alt="second diagram" width="800" height="697"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>openai</category>
      <category>vectordatabase</category>
      <category>ai</category>
      <category>node</category>
    </item>
    <item>
      <title>Model Context Protocol: Parte I Creando un Servidor MCP</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Wed, 23 Apr 2025 22:34:06 +0000</pubDate>
      <link>https://dev.to/jeanvittory/model-context-protocol-parte-i-creando-un-servidor-mcp-4hdg</link>
      <guid>https://dev.to/jeanvittory/model-context-protocol-parte-i-creando-un-servidor-mcp-4hdg</guid>
      <description>&lt;p&gt;Anthropic ha lanzado un nuevo protocolo de comunicación para modelos de IA llamado &lt;a href="https://modelcontextprotocol.io/introduction" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; el cual nos permite brindarle información, contexto y herramientas a LLMs que requieren flujos complejos para lograr respuestas fnales.&lt;/p&gt;

&lt;p&gt;En este ciclo de post veremos como crear servidores MCP (MCP Servers) y clientes MCP (MCP Clients) para lograr, por medio de un prompt, que un LLM pueda acceder a herramientas creadas por nosotros y, a través de ellas, se conecte a APIS externas para obtener información en tiempo real o ejecute cualquier código que nosotros construyamos y así lograr una respuesta con base en dicha información consultada. &lt;/p&gt;

&lt;h2&gt;
  
  
  Model Context Protocol
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqsqkvf6k36fcqa8tup2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhqsqkvf6k36fcqa8tup2.png" alt="Diagram" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Como puedes ver en la imágen anterior tenemos algo llamado clientes MCP que se conectan, a través del protocolo MCP, a un servidor MCP y este tiene acceso a bases de datos, sistemas de archivos o puede realizar llamadas a una API externa. Pero, ¿qué es un cliente y un servidor MCP?&lt;/p&gt;

&lt;h2&gt;
  
  
  Cliente MCP
&lt;/h2&gt;

&lt;p&gt;Un cliente MCP es básicamente Claude Desktop, Github Copilot o en su defecto una aplicación o componente de software creado por nosotros mismos. Este cliente es el encargado de recibir el prompt del usuario y conectarse al servidor MCP el cual es el que almacena en su interior las &lt;a href="https://modelcontextprotocol.io/docs/concepts/tools" rel="noopener noreferrer"&gt;Tools&lt;/a&gt; que están disponibles para ser ejecutadas y realizar acciones ya sea sobre nuestro sistema de archivos, una base de datos o solicitudes a través de la web.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Los permisos de estas acciones deben estar configurados en nuestro servidor MCP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Servidor MCP
&lt;/h2&gt;

&lt;p&gt;Dicho lo anterior, el servidor MCP es el encargado de almacenar tres tipos de datos para que estén disponibles al cliente MCP:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Prompts&lt;/li&gt;
&lt;li&gt;Tools&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hasta ahora hemos hecho un esbozo de lo que significan las Tools en el contexto de los MCP pero ¿Qué son los Resources y los Prompts?&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Los Resources o Recursos son datos que expone el servidor al cliente para que este los lea y use como contexto para el LLM. En ese sentido los recursos pueden ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Archivos&lt;/li&gt;
&lt;li&gt;Registros de bases de datos&lt;/li&gt;
&lt;li&gt;Respuestas de una API&lt;/li&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;entre otros&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Si deseas aprender más sobre Resources lee &lt;a href="https://modelcontextprotocol.io/docs/concepts/resources" rel="noopener noreferrer"&gt;esto&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prompts
&lt;/h2&gt;

&lt;p&gt;Los prompts en este contexto son prompts templates que podemos almacenar en nuestro servidor y exponerlos al cliente para que los pueda usar y, de esta forma, mantener una cohesión a la hora de realizar ciertas tareas dentro del flujo de nuestro sistema.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si deseas aprender más sobre Prompts lee &lt;a href="https://modelcontextprotocol.io/docs/concepts/prompts" rel="noopener noreferrer"&gt;esto&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;Por último, las Tools. Debemos pensar estas como funcionalidades almacenadas en nuestro servidor MCP para poder ejecutar diferentes tipos de operaciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interactuar con APIS externas.&lt;/li&gt;
&lt;li&gt;Realizar operaciones de computo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En otras palabras, estas herramientas no son más que funciones, como las que hemos construido siempre, almacenadas y listas para ser ejecutadas por el servidor MCP si el LLM asi lo decide.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si deseas aprender más sobre Tools lee &lt;a href="https://modelcontextprotocol.io/docs/concepts/tools" rel="noopener noreferrer"&gt;esto&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En este post estarémos enfocados en el uso de Tools principalmente.&lt;/p&gt;

&lt;p&gt;Ahora bien, ¿cómo definimos estas herramientas en nuestro servidor MCP? Cuando usamos algún sdk de &lt;a href="https://github.com/modelcontextprotocol" rel="noopener noreferrer"&gt;model context protocol&lt;/a&gt; la definición de estas herramientas tiene su propia sintaxis las cuales puedes ver aquí:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/python-sdk" rel="noopener noreferrer"&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/typescript-sdk" rel="noopener noreferrer"&gt;Typescript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/java-sdk" rel="noopener noreferrer"&gt;Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/kotlin-sdk" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/modelcontextprotocol/csharp-sdk" rel="noopener noreferrer"&gt;C#&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Para nuestro caso definiremos nuestro servidor MCP usando el SDK de Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;utils.helpers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fetch_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;search_web&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;  &lt;span class="n"&gt;SERPER_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_urls&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Search the documentation for a given query and library,
    Supports Langchain, Openai, llama-index.

    Args:
        query: The query to search (e.g: Chroma DB)
        library: The library search in (e.g: Langchain)

    Returns:
        The information from the documentation.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;docs_urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The library &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not supported.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;docs_urls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;search_web_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SERPER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Something went wrong: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; 

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En el código anterior podemos observar 5 aspectos importantes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;La definición de nuestro MCP server.&lt;/li&gt;
&lt;li&gt;La creación de una herramienta o Tool para nuestro MCP Server.&lt;/li&gt;
&lt;li&gt;Validaciones dentro de nuestro MCP server.&lt;/li&gt;
&lt;li&gt;La creación de funciones utilitarias para modularizar la tarea de nuestra herramienta.&lt;/li&gt;
&lt;li&gt;El runner de nuestro MCP Server utilizando un concepto que veremos mas adelante llamado transport.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;veamos punto por punto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definición de nuestro MCP server
&lt;/h2&gt;

&lt;p&gt;Para crear nuestro servidor es necesario instalar la dependencia necesaria &lt;/p&gt;

&lt;p&gt;&lt;code&gt;pip install "mcp[cli]"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Una vez instalada debemos importar la clase FastMCP así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y una vez importada instanciamos nuestro servidor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;El parámetro name puede ser cualquiera que desees&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Creación de herramienta
&lt;/h2&gt;

&lt;p&gt;Para crear una herramienta necesitamos definir una función y anteponer la directiva:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto le permite al servidor identificar que la función es una herramienta y así diferenciarla de los resources o prompts que vimos anteriormente. &lt;/p&gt;

&lt;p&gt;Por otro lado es importante definir el docstring de manera clara, correcta y precisa. Proveer metadata sobre lo que hace la función, los argumentos que recibe y lo que se espera devolver como resultado. Entre más especifica sea la información que le proveamos al docstring el LLM tendrá más contexto para saber que herramienta ejecutar según el prompt que se haya usado.&lt;/p&gt;

&lt;p&gt;En nuestro caso la herramienta recibe dos parámetros &lt;code&gt;query&lt;/code&gt; y &lt;code&gt;library&lt;/code&gt; los cuales son:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Query: Tópico que se desea consultar.&lt;/li&gt;
&lt;li&gt;Library: Documentación o librería sobre la cual se desea consultar el tópico.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Estos dos parámetros son los que el LLM debe de identificar sobre el prompt utilizado y para ello es necesario una buena descripción de la herramienta usando los docstring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validaciones dentro de nuestro MCP server
&lt;/h2&gt;

&lt;p&gt;Como puedes observar una herramienta de nuestro servidor MCP se comporta como una función comun y corriente por lo tanto dentro de ella podemos realizar validaciones y manejar errores de una manera convencional. En este caso levantamos dos casos de error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;library&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;docs_urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The library &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not supported.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Something went wrong: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para la primera validación usamos una variable llamada docs_urls para centralizar y modularizar las posibles librerías que el usuario puede consultar. Esta variable no es mas que una lista definida así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;docs_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;langchain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python.langchain.com/docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama-index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docs.llamaindex.ai/en/stable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform.openai.com/docs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este punto es bueno realizar énfasis en que al tener unos buenos metadatos en el docstring podemos lograr que el modelo identifique dentro del prompt la librería que el usuario desea consultar y así logre almacenarla dentro del parámetro &lt;code&gt;library&lt;/code&gt; y de esa forma nosotros poder usarlo dentro de nuestras validaciones internas. &lt;/p&gt;

&lt;h2&gt;
  
  
  Funciones utilitarias
&lt;/h2&gt;

&lt;p&gt;Como ya hemos resaltado nos encontramos frente a una función de python comun y corriente la cual podemos manipular de tal forma que nos permite modularizar los procesos que esta necesite realizar. En nuestro caso hemos abstraido dos procesos &lt;code&gt;search_web&lt;/code&gt; y &lt;code&gt;fetch_url&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para la primera función utilizamos &lt;a href="https://serper.dev/" rel="noopener noreferrer"&gt;Serper&lt;/a&gt; como buscador de google con el fin de recuperar las 2 primeras URLs que coincidad con la busqueda del usuario. Para esto lo hacemos asi&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;site:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;docs_urls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;search_web_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SERPER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;formateamos la variable &lt;code&gt;query&lt;/code&gt; utilizando un operador de busqueda avanzada de google el cual nos permite buscar solo dentro de la URL que recuperamos de nuestra lista asi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;docs_urls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una vez formateado esta variable lo pasamos como parámetro a la función &lt;code&gt;search_web_results&lt;/code&gt; junto con la URL de serper. Ya por dentro de la función encontramos este código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SERPER_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Timeout error searching web&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPStatusError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EL cual no es más que una llamada a la API de Serper para que nos devuelva los 2 primeros resultados de la búsqueda. &lt;/p&gt;

&lt;p&gt;Nuestra segunda función &lt;code&gt;fetch_url&lt;/code&gt; se ejecuta después de devolver los resultados de &lt;code&gt;search_web&lt;/code&gt; la propiedad que necesitamos, siguiendo la documentación de Serper, es &lt;code&gt;organic&lt;/code&gt; la cual es una lista que contiene otra pripiedad llamada &lt;code&gt;link&lt;/code&gt; y son estos links, en nuestro caso 2, los que utilizaremos como parámetros de nuestra segunda función &lt;code&gt;fetch_url&lt;/code&gt;. Lo anterior lo encontramos definido aquí:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;search_web_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;search_web&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SERPER_URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Something went wrong: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;search_web_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;organic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como podemos observar nuestra función &lt;code&gt;fetch_url&lt;/code&gt; se ejecuta dentro de un ciclo for utilizando la lista &lt;code&gt;search_web_results["organic"]&lt;/code&gt;. Ya por dentro la función &lt;code&gt;fetch_url&lt;/code&gt; luce así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;30.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; 
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Timeout Error fetching url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esta función realiza una solicitud HTTP a las URLs obtenidas en la primera función y, por medio de &lt;a href="https://beautiful-soup-4.readthedocs.io/en/latest/#quick-start" rel="noopener noreferrer"&gt;Beautiful Soup&lt;/a&gt;, parseamos el HTML a texto y lo devolvemos como respuesta de la misma función.&lt;/p&gt;

&lt;p&gt;Por último el resultado de la función &lt;code&gt;fetch_url&lt;/code&gt; lo devolvemos como respuesta final de la MCP Tool. &lt;/p&gt;

&lt;h2&gt;
  
  
  Transport
&lt;/h2&gt;

&lt;p&gt;Por último nuestro código define el tipo de transporte que nuestro MCP Server utilizará. Para nuestro caso utilizamos el transporte tipo &lt;code&gt;stdio&lt;/code&gt; el cual es un standard I/O  que se recomienda usar cuando nuestro servidor MCP esta enfocado a aplicaciones por CLI o que corren de manera local en nuestro sistema realizando integraciones. &lt;/p&gt;

&lt;p&gt;Se debe tener en cuenta de que existe otro tipo de transporte llamado &lt;code&gt;SSE&lt;/code&gt; que está enfocado a conexiones entre el servidor MCP y un cliente MCP por medio de solicitudes POST.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si deseas conocer más sobre transportes mira &lt;a href="https://modelcontextprotocol.io/docs/concepts/transports#python-server" rel="noopener noreferrer"&gt;esto&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Vale hasta aquí hemos construido nuestro servidor MCP pero surgen 2 preguntas&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;¿Cómo lo usamos?&lt;/li&gt;
&lt;li&gt;¿Dónde entra la IA en todo esto?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Como se nombro al principio estos servidores MCP se pueden utilizar ya sea con cliente creado por nosotros mismos o con alguna aplicacion de LLMs como Github Copilot o Claude Desktop. &lt;/p&gt;

&lt;p&gt;En esta primera parte explicarémos como integrar nuestro servidor MCP con Github Copilot y en una segunda parte abordaremos la creación de un cliente personalizado que utiliza Express para comunicarse con nuestro servidor MCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalación con Github Copilot
&lt;/h2&gt;

&lt;p&gt;Realmente es muy sencillo. Debemos primero asegurarnos tener las extensiones correspondientes de Github Copilot en VsCode y haber configurado nuestra cuenta para tener acceso a Copilot.&lt;/p&gt;

&lt;p&gt;Luego de esto, debemos dirigirnos al archivo, dentro de vscode, llamado settings.json el cual se puede acceder así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumlt443s89nb8c9jl5wt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fumlt443s89nb8c9jl5wt.jpeg" alt="Settings JSON" width="613" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Damos click en &lt;code&gt;Open user settings (JSON)&lt;/code&gt; y nos abrirá nuestras configuraciones de Vscode en formato JSON. Lo que harémos sera agregar al final de este archivo una nueva propiedad llamada mcp así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"documentation"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--directory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"src/main.py"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Como puedes ver nos hace falta completar dos valores &lt;code&gt;command&lt;/code&gt; y la tercera posición del array args. Para obtener estos valores sigue esto pasos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Para la propiedad command, si utilizaste &lt;code&gt;uv&lt;/code&gt; como package management, corre en tu terminal el comando &lt;code&gt;which uv&lt;/code&gt; y pega esa ruta en la propiedad &lt;code&gt;command&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;para la segunda posición del array args debes de pararte en la carpeta en donde está tu servidor MCP y correr el comando &lt;code&gt;pwd&lt;/code&gt;. Pega esa ruta en la segunda posición del array args.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Vale, pero ¿Qué es todo esto? Para poder utilizar nuestro servidor MCP con Copilot debemos especificarlo en nuestro &lt;code&gt;settings.json&lt;/code&gt; para que copilot tenga acceso a él. Para ello definimos una propiedad &lt;code&gt;mcp&lt;/code&gt; y dentro de ella otra propiedad llamada &lt;code&gt;servers&lt;/code&gt;. En esta última listaremos todos los servidores que creemos para que Copilot pueda encontrarlos. &lt;/p&gt;

&lt;p&gt;Cada servidor MCP que especifiquemos aquí debe de tener una configuración para que se corra cuando interactuemos con Copilot. Si utilizamos el ejemplo de nuestro ejercicio, al definir esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"documentation":{
      "command": "",
      "args": [
        "--directory",
        "",
        "run",
        "src/main.py"
      ]
    } 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;es como si corrieramos en nuestra terminal esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv --directory &amp;lt;la ruta de tu proyecto mcp&amp;gt; run &amp;lt;el lugar en donde está tu main.py dentro del proyecto&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Al hacer esto, abriremos nuestro Copilot dentro de vscode y configuraremos el modo &lt;code&gt;agent&lt;/code&gt; automaticamente se nos habilitará un icono al lado del input &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bcbtjf0ys2lw3uwar7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bcbtjf0ys2lw3uwar7m.png" alt="MCP icon tool" width="73" height="44"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AL dar click en este icono verémos todos nuestros MCP servers disponibles &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bumxthp90j0p13m351l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0bumxthp90j0p13m351l.png" alt="MCP server list" width="627" height="145"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Podemos habilitarlos y deshabilitarlos a nuestro gusto.&lt;/p&gt;

&lt;p&gt;Es todo, ya podemos utilizar nuestro LLM con Copilot y este tendrá acceso a los MCP habilitados en nuestro sistema para utilizarlos si considera que para lograr una respuesta correcta a nuestro prompt debe de acceder a ellos como herramientas.&lt;/p&gt;

&lt;p&gt;En una próxima entrega veremos como podemos conectar este MCP Server a un MCP Client que utiliza Express para capturar los prompts del usuario que provienen de cualquier cliente que se pueda comunicar por medio de HTTP. Logrando esto veremos el panorama completo del flujo de datos dentro del protocolo MCP utilizando un LLM de por medio para completar tareas que requiera nuestro usuario. &lt;/p&gt;

&lt;p&gt;Si desean darle un vistazo al código completo desde el repositorio pueden hacerlo desde &lt;a href="https://github.com/JeanVittory/mcp-server" rel="noopener noreferrer"&gt;aquí&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>mcp</category>
      <category>ai</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>AWS CDK + SAM: La combinación perfecta para desarrollar y depurar Lambdas localmente</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Tue, 11 Mar 2025 19:01:27 +0000</pubDate>
      <link>https://dev.to/jeanvittory/aws-cdk-sam-la-combinacion-perfecta-para-desarrollar-y-depurar-lambdas-localmente-148b</link>
      <guid>https://dev.to/jeanvittory/aws-cdk-sam-la-combinacion-perfecta-para-desarrollar-y-depurar-lambdas-localmente-148b</guid>
      <description>&lt;p&gt;Hace poco me vi en la necesidad de levantar una infraestructura de AWS usando su &lt;a href="https://aws.amazon.com/cdk/" rel="noopener noreferrer"&gt;CDK&lt;/a&gt; debido a la sencillez de la API y la rapidez con la que se pueden definir y desplegar diferentes servicios.&lt;/p&gt;

&lt;p&gt;Mi infraestructura contaba, entre otras cosas, con una serie de Lambdas conectadas a una base de datos de DynamoDB y, por lo pequeño del proyecto, decidí definir el código de las Lambdas en el mismo repositorio en donde realicé la configuración de mi infraestructura.&lt;/p&gt;

&lt;p&gt;Cuando empecé a desarrollar la lógica de negocio de mi proyecto en las Lambdas, me encontré con que el CDK no cuenta con una forma per se de permitirte levantar un servicio local en tu máquina para poder hacer debug del código en las Lambdas.&lt;/p&gt;

&lt;p&gt;Normalmente a lo que te lleva en un primer momento el flujo es a hacer debug sobre Cloudwatch, sin embargo, para ello debes de realizar un despliegue cada vez que necesitas revisar un log o solucionar un error en la etapa de desarrollo. Naturalemente no es efectivo trabajar de esta forma. &lt;/p&gt;

&lt;p&gt;Aquí es donde entra &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html" rel="noopener noreferrer"&gt;SAM&lt;/a&gt; o Serverless Application Model. En pocas palabras es un framework de AWS para crear aplicaciones serverless usando IaC a través de su CLI y, aunque tenemos a la mano otras herramientas como &lt;a href="https://www.localstack.cloud/" rel="noopener noreferrer"&gt;localStack&lt;/a&gt;, me parecio interesante probar con SAM por ser una herramienta nativa de AWS que se integra facilmente con el CDK como lo veremos mas adelante.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Como funciona SAM?
&lt;/h2&gt;

&lt;p&gt;Para poder levantar nuestros servicios definidos con el CDK  necesitamos generar un archivo &lt;code&gt;.yml&lt;/code&gt; en donde estarán descritos, en un formato YAML, todos los servicios y configuraciones que nuestra aplicación necesita.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Recordemos que cloudformation trabaja usando los servicios definidos a traves de YAML y SAM es por definicion una extensión de cloudformation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Usando este archivo &lt;code&gt;.yml&lt;/code&gt; SAM sabrá qué lambdas y servicios debe de levantar y bajo que configuraciones, permisos y conexiones a otros servicios debe contemplar. &lt;/p&gt;

&lt;p&gt;Sin embargo, la historia no termina aquí ya que en este punto aparece un elemento fundamental para el funcionamiento de SAM el cual es &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Docker le permite a SAM emular un entorno igual al que usa AWS para correr tus lambdas en sus servicios. Este entorno cuenta con librerías, un sistema operativo y configuraciones especificas del runtime que hayas seleccionado. &lt;/p&gt;

&lt;p&gt;Si bien, podrias prescindir de Docker sin embargo tendrías que configurar tu mismo el entorno en local para poder correr el código de tus lambdas lo cual te llevaría a configuraciones complejas que Docker hace por ti. &lt;/p&gt;

&lt;p&gt;En este punto podríamos enumerar los requisitos que necesitas para poder usar SAM:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI&lt;/li&gt;
&lt;li&gt;SAM CLI&lt;/li&gt;
&lt;li&gt;Docker CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting de Docker
&lt;/h2&gt;

&lt;p&gt;A veces sucede que nos acostumbramos a usar Docker Desktop, sin embargo es posible que al correr SAM este no pueda conectarse a Docker aún teniendo este último en ejecución arrojandonos un mensaje así:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Para solventar este error debemos de revisar que la variable de entorno $DOCKER_HOST este disponible en nuestro sistema y, en caso de no tenerla disponible, definirla así en tu archivo de configuraciones del sistema: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;export DOCKER_HOST=unix:///home/{user}/.docker/desktop/docker.sock&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Si estás en un sistema Linux como Ubuntu puedes conocer tu usuario con el comando &lt;code&gt;whoami&lt;/code&gt;. No olvides guardar el archivo y ejecutar: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;source ~/.bashrc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;o si usas zsh &lt;/p&gt;

&lt;p&gt;&lt;code&gt;source ~/.zshrc&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Instalando SAM
&lt;/h2&gt;

&lt;p&gt;Para instalar SAM solo basta con seguir las instrucciones en la &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;documentacion&lt;/a&gt;. Aquí te mostrare las indicaciones para un OS Linux sin embargo en la documentacion referida encontrarás los pasos necesarios para tu sistema operativo.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Descarga el ZIP que encontrarás &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html" rel="noopener noreferrer"&gt;aqui&lt;/a&gt; en el apartado de Linux.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Una vez descargado descomprimelo usando &lt;code&gt;unzip aws-sam-cli-linux-x86_64.zip -d sam-installation&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ejecuta como sudo el archivo install &lt;code&gt;sudo ./sam-installation/install&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Por último verifica la instalación con &lt;code&gt;sam --version&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Generando el archivo YML
&lt;/h2&gt;

&lt;p&gt;Para generar nuestro archivo YAML es muy sencillo. Una vez definidos nuestros servicios usando el CDK debemos ir a nuestra terminal y ejecutar el comando:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk synth&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Esto nos devolverá toda nuestra infraestructura en formato YAML sin embargo necesitamos almacenar esta información en un archivo en la raíz de nuestro proyecto por lo tanto el comando para esto debe de ser este: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;cdk synth --no-staging &amp;gt; template.yaml&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;El flag --no-staging le dice al CDK que no copie los assets necesarios para el despliegue ya que para nuestro caso no los necesitamos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Levantando nuestro servidor en local
&lt;/h2&gt;

&lt;p&gt;Nuestra plantilla de cloudformation ahora está en un archivo en la raíz de nuestro proyecto llamado &lt;code&gt;template.yml&lt;/code&gt; para que SAM  logre levantar el servidor basta con correr el comando:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sam local start-api&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;El comando anterior nos permite simular el comportamiento del servicio de AWS &lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; lo cual nos abre un canal de comunicación HTTP para probar nuestros endpoints los cuales estan conectados a las lambdas usando, en una primera instancia el CDK de AWS, pero que SAM lee a través del &lt;code&gt;template.yml&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Indicaciones finales
&lt;/h2&gt;

&lt;p&gt;Si has seguido los pasos hasta aquí es posible que te encuentres con la situación de que al realizar cambios sobre tu lambda y probarlas en tu local no veas reflejados los cambios. &lt;/p&gt;

&lt;p&gt;Esto sucede porque al correr el comando &lt;code&gt;sam local start-api&lt;/code&gt; este genera un archivo llamado &lt;code&gt;.aws-sam&lt;/code&gt; y si lo revisas te encontrarás en la carpeta build un archivo &lt;code&gt;template.yaml&lt;/code&gt; al explorarlo te darás cuenta que las lambdas tienen una propiedad llamada &lt;code&gt;Metadata&lt;/code&gt; y esta a su vez contiene una propiedad llamada &lt;code&gt;aws:asset:path&lt;/code&gt; que te dirije a la carpeta &lt;code&gt;cdk.out&lt;/code&gt; y si revisas en esta carpeta el archivo que ahí se especifica veras que el código que ésta contiene no tiene tus últimos cambios. &lt;/p&gt;

&lt;p&gt;Para solventar esto debemos crear un pequeño script que nos permita hacer un &lt;code&gt;rebuild&lt;/code&gt; de nuestro codigo actualizado para que SAM pueda leerlo. Este script luciría así: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;#!/bin/bash&lt;br&gt;
pkill -f "sam local start-api"&lt;br&gt;
rm -rf .aws-sam&lt;br&gt;
rm -rf cdk.out&lt;br&gt;
cdk synth --no-staging &amp;gt; template.yaml&lt;br&gt;
sam build&lt;br&gt;
sam local start-api&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Para nuestro caso lo podemos colocar en la raíz del proyecto y crear en nuestro &lt;code&gt;package.json&lt;/code&gt; un script &lt;code&gt;dev&lt;/code&gt; que lo ejecute cada vez que levantemos nuestro entorno de desarrollo. &lt;/p&gt;

&lt;p&gt;Esto seria todo. Recuerda siempre tener Docker levantado para que el proceso sea exitoso. &lt;/p&gt;

</description>
      <category>aws</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>docker</category>
    </item>
    <item>
      <title>JWT &amp; Refresh Tokens 🔒</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Sun, 24 Mar 2024 00:35:12 +0000</pubDate>
      <link>https://dev.to/jeanvittory/jwt-refresh-tokens-2g3d</link>
      <guid>https://dev.to/jeanvittory/jwt-refresh-tokens-2g3d</guid>
      <description>&lt;p&gt;JWT o Json Web Token es un estándar para la autenticación y transferencia segura de datos entre dos partes. Usa la encriptación en base64 y una firma por medio de una clave privada o publico/privada para poder verificar la validez del token.&lt;/p&gt;

&lt;p&gt;Estos tokens cuentan con una estructura en particular&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74kf9hr5pymmjlls5oma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F74kf9hr5pymmjlls5oma.png" alt="Image description" width="800" height="52"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;El header o encabezado contiene la información del tipo de token utilizado y el tipo de algoritmo usado para la encriptación. Por otro lado, el payload o carga útil hace referencia a los datos suministrados por el desarrollador y cargados dentro del token en formato JSON. Por ultimo tenemos el signature o la firma que consiste en una clave secreta para ser usada como método de verificación de la validez del token. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Para mayor información referirse a: &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;https://jwt.io/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ahora bien, en el contexto de una aplicación que requiera un proceso de autenticación y autorización de usuarios por lo general hacemos uso de JWT para generar tokens los cuales cargamos, dentro del payload del token, con información que nos permita verificar en nuestra base de datos que esa persona sea quien dice ser o cuente con los roles y permisos que se requieren para acceder a un recurso en especifico.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Es importante resaltar que el payload utilizado no debe contener información susceptible del usuario como contraseñas, cuentas bancarias entre otras. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Para lograr esto por lo general tenemos un flujo similar a este &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yoqto0fl3dxsxmj58yy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yoqto0fl3dxsxmj58yy.png" alt="Image description" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la anterior imagen observamos como un cliente inicia sesión dentro de nuestra aplicación, enviando mediante una solicitud HTTP, un email y una contraseña la cual verificamos en nuestra base de datos y si dichos datos coinciden devolvemos un &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; el cual el cliente almacenara, ya sea en el local storage o en el session storage, y deberá enviar en cada solicitud que realice dentro de nuestra aplicación para autorizar el consumo de los recursos que requiera. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;La forma en como enviar el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; desde el servidor dependerá de tu aplicación. Este token puede ser enviado mediante cookies o mediante una respuesta HTTP ordinaria. Cada una tiene ventajas y desventajas que deben ser consideradas.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Este &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; contara con un tiempo de expiración el cual configuraremos de acuerdo a las necesidades tanto de seguridad como de experiencia de usuario que nuestra aplicación requiera. &lt;/p&gt;

&lt;p&gt;Debemos considerar que entre mas largo sea el tiempo de expiración de nuestros tokens mas vulnerable será nuestra aplicación en términos de seguridad. Por otro lado, entre mas corto sea dicho tiempo, nuestros usuarios deberán hacer login nuevamente para obtener un &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; nuevo lo cual entra en detrimento de la experiencia de usuario.&lt;/p&gt;

&lt;h3&gt;
  
  
  REFRESH TOKENS
&lt;/h3&gt;

&lt;p&gt;Hasta este punto no se ha dicho nada diferente a lo que es normalmente un sistema de autenticación y autorización común que implemente JWT. &lt;/p&gt;

&lt;p&gt;Los refresh tokens nos permiten lograr que nuestro usuario no tenga que realizar un login nuevamente cada que el ACCESS_TOKEN expira y lo logra mediante el envío de un token extra o REFRESH_TOKEN cuando el usuario hace login. &lt;/p&gt;

&lt;p&gt;La diferencia a este punto es que ambos tokens tienen un tiempo de expiración diferente. Mientras el ACCESS_TOKEN puede llegar a tener 20s el REFRESH_TOKEN puede llegar a tener 6 meses o 1 año de expiración y al vencerse el primer token el usuario utilizara el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; como token de acceso para generar un nuevo &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; y de esta forma mantener activa la sesión dentro de nuestra aplicación. &lt;/p&gt;

&lt;p&gt;Es posible llegar a pensar que esta solución no es realmente útil ya que simplemente aumentando el tiempo de expiración del &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; nos ahorraría tener que enviar un segundo token o podríamos pensar ¿Qué sucede si el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; es hurtado? ¿No pondríamos en riesgo la seguridad de nuestro sistema al tener un tiempo de expiración tan largo?&lt;/p&gt;

&lt;h3&gt;
  
  
  ¿Por qué enviar dos tokens de acceso?
&lt;/h3&gt;

&lt;p&gt;Es importante resaltar que el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; nos permite manejar un concepto llamado SESIONES dentro de nuestra aplicación la cual por lo general es una tabla en nuestra base de datos. &lt;/p&gt;

&lt;p&gt;La idea de generar un segundo token es poder mediante este token generar un &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; nuevo para el usuario en caso de que dicho &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; haya sido expirado y solo generaremos un nuevo &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; si el usuario nos envía el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; expirado y el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; existe dentro de nuestro registro de sesiones y es valido como JWT. &lt;/p&gt;

&lt;p&gt;Dicho lo anterior, nuestro proceso de autorización ahora no solo consultara la validez del &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; y del &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; o si el usuario que esta accediendo a los recursos de nuestra aplicación existe, sino que ahora consultara una tabla llamada SESIONES que almacenara los &lt;code&gt;REFRESH_TOKENS&lt;/code&gt; activos y, si dicho token existe y esta activo, generara un nuevo &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; para acceder a los recursos de nuestra aplicación. &lt;/p&gt;

&lt;p&gt;De esta forma estaremos generando &lt;code&gt;ACCESS_TOKENS&lt;/code&gt; continuamente solo y solo si nuestro usuario cuente con un &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; valido y un &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; valido. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Debemos resaltar que no es lo mismo validez del token que tiempo de expiración del token. Nuestro token pudo haber expirado pero sigue siendo valido. Los JWT no pueden ser invalidados y solo podrán ser manejados mediante un tiempo de expiración lo cual no necesariamente quiere decir invalidez&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ¿Qué sucede si el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; es hurtado? ¿No pondríamos en riesgo la seguridad de nuestro sistema al tener un tiempo de expiración tan largo?
&lt;/h3&gt;

&lt;p&gt;Recordemos que la forma de acceder a nuestra aplicación es mediante el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; y si este ha expirado generaremos otro nuevo mediante la validación del &lt;code&gt;REFRESH_TOKEN&lt;/code&gt;. Por lo tanto solo podemos generar un nuevo &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; si contamos tanto con el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; y el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; valido. &lt;/p&gt;

&lt;p&gt;Si un atacante logra hurtar nuestro &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; solo tardara 5s, o menos si así lo configuramos, para ser expulsado de la aplicación ya que no podrá generar uno nuevo al no tener el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Por otro lado, si un atacante logra hurtar nuestro REFRESH_TOKEN no podrá generar un &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; ya que, para generar uno, necesita el primer &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; generado al momento de hacer login. &lt;/p&gt;

&lt;p&gt;Podemos observar aquí la importancia de tener un buen manejo de seguridad en nuestros tokens como por ejemplo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configurar tiempos cortos en el ACCESS_TOKEN.&lt;/li&gt;
&lt;li&gt;Transferir nuestros tokens mediante las cabeceras HTTP.&lt;/li&gt;
&lt;li&gt;Si usamos cookies asegurarnos de usar la bandera HttpOnly y secure para evitar ataques XSS.&lt;/li&gt;
&lt;li&gt;Limitar el tiempo de expiración de nuestras cookies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Llegados a este punto encontramos un flujo similar a este&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiaryumy4e5ei0w1j01mh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiaryumy4e5ei0w1j01mh.png" alt="Image description" width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora veremos de que forma, desde el código, podemos actualizar nuestro &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; haciendo uso de nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  IMPLEMENTACIÓN
&lt;/h3&gt;

&lt;p&gt;En este ejemplo utilizaremos Node y Express para ejemplificar este proceso y haremos toda nuestra lógica dentro del middleware de autorización. Debemos recordar que nuestro proceso de autenticación, donde estamos generando nuestros dos tokens, ya esta configurado y nos centraremos en como actualizar nuestro &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; utilizando nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Sin embargo, debemos realizar un pequeño ajuste a nuestro proceso de autenticación ya que es aquí donde haremos el registro de nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; en la tabla que anteriormente llamamos &lt;code&gt;SESIONES&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;El código luce algo así: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3imndpnfw3ze1d0epp7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3imndpnfw3ze1d0epp7m.png" alt="Image description" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Podemos observar como nuestro servicio de autenticación, que es llamado desde nuestro controlador de login, primero valida la existencia del usuario a través del método &lt;code&gt;getUserByEmail&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Utilizando la librería bcrypt validamos la contraseña y, si este proceso arroja un error, devolvemos a través del manejador de errores el código correspondiente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si la validación de la contraseña fue bien registramos en nuestra tabla &lt;code&gt;SESIONES&lt;/code&gt;, usando el método &lt;code&gt;createSession&lt;/code&gt;, la sesión de nuestro usuario.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Como ultimo paso, firmamos ambos tokens y configuramos el tiempo de expiración para cada uno. Recordemos que el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; por lo general cuenta con un tiempo muy corto de expiración y el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt;, por el contrario, se configura con un tiempo largo de expiración. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;De esta forma tenemos nuestro proceso de autenticación ahora configurado para que nos devuelva ambos tokens y nos registre una sesión por cada usuario con un login activo. &lt;/p&gt;

&lt;h3&gt;
  
  
  Proceso de autorización
&lt;/h3&gt;

&lt;p&gt;Por lo general tendemos a realizar el proceso de autorización, para cada uno de los recursos de nuestra aplicación, mediante un middleware. Es en este en donde en este ejemplo realizaremos todo el proceso de validación y generación &lt;code&gt;ACCESS_TOKEN&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Una vez nuestro usuario haya accedido a nuestra aplicación y este haya guardado el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; y el &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; utilizaremos nuestro middleware de autorización para validar si nuestro usuario cuenta con los permisos suficientes para consultar el recurso que esta solicitando. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftziktek7jg0vwd2qil91.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftziktek7jg0vwd2qil91.png" alt="Image description" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En la anterior imagen observamos todo el proceso de autorización completo pero vamos a revisarlo linea por linea&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;En este ejemplo estamos enviando los tokens por medio de el header &lt;code&gt;Authorization&lt;/code&gt; y un header personalizado que llamamos &lt;code&gt;x-refresh-token&lt;/code&gt; por lo tanto lo primero que realizamos es la validación acerca de la existencia o no de ambos tokens. Si estos no existen, enviaremos un error de tipo &lt;code&gt;Forbbiden&lt;/code&gt;. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;En las siguientes lineas tenemos una segunda capa de validación en donde, usando la librería JOI, validamos si los datos dentro de estas cabeceras son de tipo &lt;code&gt;string&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;dentro del &lt;code&gt;try...catch&lt;/code&gt; verificamos el &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; y accedemos a los valores &lt;code&gt;payload&lt;/code&gt; y &lt;code&gt;expired&lt;/code&gt;. Si el payload existe, creamos un valor nuevo dentro del objeto &lt;code&gt;req&lt;/code&gt; que llamamos &lt;code&gt;user&lt;/code&gt; y lo cargamos con lo que viene en el payload. Por último, ejecutamos la función &lt;code&gt;next&lt;/code&gt; la cual nos permite avanzar al controlador. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Observemos que existe un @ts-ignore ya que para crear valores dentro del objeto &lt;code&gt;Request&lt;/code&gt; de Express debemos realizar una configuración extra la cual no entra en el contexto de este blog. Si deseas saber más al respecto puedes leer como hacerlo &lt;a href="https://dev.to/jeanvittory/expandiendo-la-interfaz-%20request-de-express-con-dts-opn"&gt;aquí&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Si ocurrió algún problema con la verificación de nuestro &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; nuestro método verifyJWT nos devolverá un objeto con el payload &lt;code&gt;null&lt;/code&gt; y el expired &lt;code&gt;jwt expired&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfosxvi0iyen78mfbpbx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flfosxvi0iyen78mfbpbx.png" alt="Image description" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Luego de esto validamos de que si nuestra propiedad &lt;code&gt;expired&lt;/code&gt; tiene un valor &lt;code&gt;truthy&lt;/code&gt; y nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; fue validado con éxito validaremos nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; usando el método &lt;code&gt;verifyWT&lt;/code&gt;. Caso contrario devolveremos un objeto con una propiedad payload en &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Si nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; no fue validado con éxito devolveremos un error de &lt;code&gt;Unauthorized&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Caso contrario, avanzaremos con nuestro flujo y consultaremos en nuestra base de datos el &lt;code&gt;sessionId&lt;/code&gt; que venia en el payload de nuestro &lt;code&gt;REFRESH_TOKEN&lt;/code&gt; y si esta consulta no encuentra ningún resultado volveremos a arrojar un error de tipo &lt;code&gt;Unauthorized&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Por el contrario, si nuestra consulta encuentra un sessionId activo firmaremos un nuevo &lt;code&gt;ACCESS_TOKEN&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;El payload de este nuevo token lo ingresaremos en la propiedad user de nuestro objeto &lt;code&gt;Request&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Por último, agregaremos este nuevo &lt;code&gt;ACCESS_TOKEN&lt;/code&gt; dentro de la cabecera &lt;code&gt;Authorization&lt;/code&gt; concatenando la palabra &lt;code&gt;Bearer&lt;/code&gt; al inicio. Finalizaremos ejecutando la función next  para avanzar a nuestro controlador.    &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;De esta forma podemos crear un sistema de tokens el cual hace uso de los &lt;code&gt;REFRESH_TOKEN&lt;/code&gt;. Si bien este sistema no es infalible podemos mejorar ciertos aspectos de nuestro sistema como lo hemos visto a través de esta lectura.  &lt;/p&gt;

&lt;p&gt;Gracias por leer 😊&lt;/p&gt;

</description>
      <category>jwt</category>
      <category>typescript</category>
      <category>node</category>
      <category>backend</category>
    </item>
    <item>
      <title>Utiliza el patrón de diseño Strategy 👾</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Sat, 09 Dec 2023 20:13:55 +0000</pubDate>
      <link>https://dev.to/jeanvittory/utiliza-el-patron-de-diseno-strategy-2e27</link>
      <guid>https://dev.to/jeanvittory/utiliza-el-patron-de-diseno-strategy-2e27</guid>
      <description>&lt;p&gt;El patrón de diseño Strategy es una técnica utilizada para definir una serie de algoritmos, encapsularlos y así poder hacerlos intercambiables de acuerdo a las necesidades del usuario.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Este diseño hace parte de la familia de patrones comportamentales.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Podemos ejemplificar lo anterior imaginando que necesitamos definir, de acuerdo al método de pago de nuestro cliente, una serie de cálculos y acciones para poder ejecutar la transacción final. &lt;/p&gt;

&lt;p&gt;A veces lo primero que se nos viene a la mente es utilizar un &lt;code&gt;if...else&lt;/code&gt; y a partir de un parámetro de entrada definir distintos caminos en nuestro programa. Si seguimos esta idea pronto nos encontraremos con problemas de legibilidad, escalado y la refactorización puede llegar a ser compleja dependiendo de la complejidad de nuestro programa.&lt;/p&gt;

&lt;p&gt;Por lo tanto el patrón Strategy nos permite encapsular acciones, según el método de pago elegido por nuestro cliente en nuestro ejemplo, y llamar una estrategia u otra. Si necesitamos ingresar otro método de pago que conlleve una serie de cálculos y procesos nuevos sólo basta con crear otra estrategia, con su respectivo algoritmo encapsulado y llamarlo. &lt;/p&gt;

&lt;h2&gt;
  
  
  Implementación
&lt;/h2&gt;

&lt;p&gt;Para lograr lo anterior debemos partir de la idea de que el patrón Strategy usa como punto de partida una interfaz compartida por todas las diferentes estrategias que queramos crear.&lt;/p&gt;

&lt;p&gt;Continuando con el ejemplo anterior, nuestra acción inicial es el proceso de pagos por lo tanto sabemos que esa acción será compartida por nuestras estrategias. Debemos generar una interfaz que describa dicha acción:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export interface IPaymentMethod {
  processPayment(paymentQuantity: number): Promise&amp;lt;string&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Esta función nos devolverá un mensaje de ejemplo pero puedes modificarlo según sea tu caso&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Luego de esto debemos crear nuestras estrategias las cuales implementarán en su interior un método, tal como lo describe la interfaz que creamos anteriormente, que se encargará de procesar, calcular y ejecutar lo que necesitemos según sea el caso.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  IPaymentMethod,
  debitCardTypes,
  delaysDebitCards,
  creditCardTypes,
  delaysCreditCards,
} from "./interfaces";

export class CashStrategy implements IPaymentMethod {
  processPayment(paymentQuantity: number): Promise&amp;lt;string&amp;gt; {
    return new Promise((resolve) =&amp;gt; {
      try {
        setTimeout(() =&amp;gt; {
          resolve(
            `The payment was succesfully. You paid ${paymentQuantity} in cash`
          );
        }, 1000);
      } catch (error: any) {
        resolve("error");
      }
    });
  }
}

export class DebitCardStrategy implements IPaymentMethod {
  private debitCard: string;

  constructor(debitCardTypeInput: string) {
    this.debitCard = debitCardTypeInput;
  }

  processPayment(paymentQuantity: number): Promise&amp;lt;string&amp;gt; {
    const selectedCard = this.debitCard;
    if (
      Object.values(debitCardTypes).includes(selectedCard as debitCardTypes)
    ) {
      const delay = delaysDebitCards[selectedCard as debitCardTypes];
      return new Promise((resolve, reject) =&amp;gt; {
        try {
          setTimeout(() =&amp;gt; {
            resolve(
              `The payment was succesfully. You paid ${paymentQuantity} with ${selectedCard}`
            );
          }, delay);
        } catch (error: any) {
          reject("error");
        }
      });
    } else {
      throw "The selected card do not exist.";
    }
  }
}

export class CreditCardStrategy implements IPaymentMethod {
  private creditCard: string;

  constructor(creditCardTypeInput: string) {
    this.creditCard = creditCardTypeInput;
  }

  processPayment(paymentQuantity: number): Promise&amp;lt;string&amp;gt; {
    const selectedCard = this.creditCard;
    if (
      Object.values(creditCardTypes).includes(selectedCard as creditCardTypes)
    ) {
      const delay = delaysCreditCards[selectedCard as creditCardTypes];
      return new Promise((resolve, reject) =&amp;gt; {
        try {
          setTimeout(() =&amp;gt; {
            resolve(
              `The payment was succesfully. You paid ${paymentQuantity} with ${selectedCard}`
            );
          }, delay);
        } catch (error: any) {
          reject("error");
        }
      });
    } else {
      throw "The selected card do not exist.";
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Hemos utilizado un setTimeout para simular un retraso en cada estrategia y simular un comportamiento asíncrono&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Luego de crear las estrategias debemos crear la clase que instanciara nuestros pagos y desde allí ejecutaremos o seleccionaremos la estrategia que necesitemos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { CashStrategy } from "./strategys";
import { IPaymentMethod, IPayments } from "./interfaces";

export class Payments implements IPayments {
  public paymentMethod: IPaymentMethod = new CashStrategy();

  setPaymentMethod(paymentStrategy: IPaymentMethod): void {
    this.paymentMethod = paymentStrategy;
  }

  applyTransaction(paymentQuantity: number): Promise&amp;lt;string&amp;gt; {
    return this.paymentMethod.processPayment(paymentQuantity);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Por defecto nuestra clase Payment instancia CashStrategy que se encarga de procesar transacciones en efectivo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Por ultimo solo nos queda ejecutar nuestro programa e instanciar una u otra estrategia según sea el caso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { Payments } from "./payments";
import { DebitCardStrategy, CreditCardStrategy } from "./strategys";

const payment = new Payments();

const init = async () =&amp;gt; {
  payment.setPaymentMethod(new CreditCardStrategy("American Express"));
  return await payment.applyTransaction(200);
};
init().then((result) =&amp;gt; console.log(result));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Desventajas
&lt;/h2&gt;

&lt;p&gt;Una de las principales desventajas de este patrón de diseño es que agrega un nivel de complejidad adicional al código en especial cuando tenemos múltiples estrategias y debemos coordinarlas entre ellas. &lt;/p&gt;

&lt;p&gt;Al tratarse de clases, la configuración inicial y la conservación del estado entre las instancias puede llegar a ser un proceso complejo así como la compilación de nuestro programa cuando nuestras clases empiezan a aumentar.&lt;/p&gt;

&lt;p&gt;A pesar de todo esto el patrón de estrategia nos permite solventar múltiples situaciones y si sabemos aplicarlos nos puede brindar soluciones a diferentes problemas que afrontamos como programadores.&lt;/p&gt;

&lt;p&gt;Te adjunto el repositorio donde encontraras el código de este ejemplo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://codesandbox.io/p/sandbox/strategy-pattern-8vcr2q" rel="noopener noreferrer"&gt;Repositorio&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>designpatterns</category>
      <category>programming</category>
    </item>
    <item>
      <title>Expandiendo la interfaz Request de Express con .d.ts</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Fri, 26 May 2023 16:15:35 +0000</pubDate>
      <link>https://dev.to/jeanvittory/expandiendo-la-interfaz-request-de-express-con-dts-opn</link>
      <guid>https://dev.to/jeanvittory/expandiendo-la-interfaz-request-de-express-con-dts-opn</guid>
      <description>&lt;p&gt;Hace poco me enfrente a una situación en la cual tenía un proyecto en Express con Typescript y uno de mis middlewares, especificamente el de autorización, debía obtener, de un JWT, el email del usuario y pasarlo al controlador de alguna forma.&lt;/p&gt;

&lt;p&gt;Quería que dicho email llegara al controlador ya con el campo email dentro del objeto request de express con el fin de poder desestructurarlo de una manera más sencilla pero al intentar hacerlo me encontraba con el siguiente error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle2mkyp8n6ny07u6f815.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle2mkyp8n6ny07u6f815.png" alt="Error 1" width="732" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¿Porqué sucede esto? Estamos intentando agregar un campo desconocido para el parámetro request de express, debemos recordar que estos campos están definidos en interfaces o tipos que el mismo express nos provee cuando instalamos la dependencia, por lo tanto, debemos lograr que dicha interfaz contemple el valor que queremos suministrarle a request y de esta forma el compilador de typescript nos permitirá agregar el campo que necesitamos ya que lo reconocerá dentro de la interfaz. &lt;/p&gt;

&lt;h2&gt;
  
  
  Usando archivos .d.ts
&lt;/h2&gt;

&lt;p&gt;Para lograr el anterior objetivo podemos hacer uso de los archivos &lt;em&gt;definition types&lt;/em&gt; de typescript. Estos archivos son aquellos que tienen como extensión &lt;code&gt;.d.ts&lt;/code&gt; y son utilizados para definir tipos de datos y algunos casos de uso podrían ser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Definir tipos de datos de librerías que están escritas en Javascript y se necesita que el compilador de typescript reconozca los datos de dicha librería. Un ejemplo de esto es el repositorio de &lt;a href="https://github.com/DefinitelyTyped/DefinitelyTyped" rel="noopener noreferrer"&gt;definitelyTyped&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Migrar aplicaciones de Javascript a Typescript progresivamente. Los archivos &lt;code&gt;.d.ts&lt;/code&gt; nos facilitan la migración de aplicaciones de Javascript a typescript al permitirnos centralizar los tipos de datos en archivos específicos, obteniendo así una fuente de documentación viva a la cual remitirnos con facilidad durante el proceso de migración.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Debemos resaltar que los archivos &lt;code&gt;.d.ts&lt;/code&gt; no generan código de salida por lo tanto estos no terminaran en el código .js final.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;En síntesis, un archivo definition type &lt;code&gt;.d.ts&lt;/code&gt; nos facilita definir tipos tanto de nuestras aplicaciones como de librerías de terceros como lo veremos a continuación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extendiendo request de Express
&lt;/h2&gt;

&lt;p&gt;Para poder solucionar el error mostrado anteriormente, cuando intentábamos crear un campo en el objeto request de express, debemos primero crear una carpeta que contendrá nuestros &lt;em&gt;definitions types&lt;/em&gt;. En mi caso generé una carpeta &lt;code&gt;@types&lt;/code&gt; en mi carpeta de configuración de express y dentro de ella sub carpetas para cada librería o archivo que necesite un &lt;code&gt;.d.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- src
  ... otros archivos
  - config
    - @types 
      - express
        - custom.d.ts
      ... otras carpetas para definición de tipos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dentro de este archivo &lt;code&gt;custom.d.ts&lt;/code&gt; debo generar el siguiente código:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;declare namespace Express {
  interface Request {
    user: {
      email: string;
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puedes observar que estamos haciendo uso de &lt;code&gt;declare namespace Express&lt;/code&gt; y dentro de las llaves definimos una interface de typescript, en este caso con el nombre Request, para agregarle los campos que necesitamos.&lt;/p&gt;

&lt;p&gt;Al hacer uso de &lt;code&gt;declare namespace Express&lt;/code&gt; estamos declarando un espacio de nombres, en este caso para la librería de Express, esto nos permite decirle al compilador de typescript que todo lo que declaremos dentro de este espacio corresponde a la librería de Express y de esta forma evitar colisiones, mejorar la modularidad de nuestro código y estructurar mejor las definiciones de tipos.&lt;/p&gt;

&lt;p&gt;Ahora bien, debemos remitirnos a nuestro archivo package.json para definir la ubicación de nuestros tipos de datos y para ello debemos hacer uso de la propiedad &lt;em&gt;"compilerOptions"&lt;/em&gt; así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  ... 
  "compilerOptions": {
    "typeRoots": [
      "./src/config/@types"
    ]
  },
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De esta forma el error desaparecerá y podremos agregar los campos que necesitemos al request de Express y así typescript podrá compilar nuestro código sin problemas.&lt;/p&gt;

&lt;p&gt;Esto es todo. Espero sea de utilidad. &lt;/p&gt;

&lt;p&gt;Cheers! :) &lt;/p&gt;

</description>
      <category>express</category>
      <category>backend</category>
      <category>node</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Acelera tus apps con Lazy Loading 🚀</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Tue, 23 May 2023 05:17:58 +0000</pubDate>
      <link>https://dev.to/jeanvittory/acelera-tus-apps-con-lazy-loading-334k</link>
      <guid>https://dev.to/jeanvittory/acelera-tus-apps-con-lazy-loading-334k</guid>
      <description>&lt;p&gt;Por lo general cuando creamos aplicaciones con React tendemos a usar rutas para que el usuario pueda acceder a diferentes vistas de nuestra aplicación. Para ello usamos, por ejemplo, el clásico navegador en la parte superior en donde podemos acceder al &lt;em&gt;home&lt;/em&gt;, &lt;em&gt;contacts&lt;/em&gt;, &lt;em&gt;products&lt;/em&gt; o cualquier vista que queremos que el usuario acceda mediante un botón. &lt;/p&gt;

&lt;p&gt;Nuestro código suele verse algo así cuando intentamos realizar el ejemplo anterior:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifotcsdjhipt90sujo6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fifotcsdjhipt90sujo6e.png" alt="Image description" width="492" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A simple vista pareciera que todo marcha bien, sin embargo cuando nuestra aplicación empieza a crecer y debemos incluir más rutas para nuestros usuarios nuestra aplicación empieza a perder rendimiento debido a que cada vez que ingresamos a ella cargamos todos nuestros componentes.&lt;/p&gt;

&lt;p&gt;Podríamos detenernos y preguntarnos ¿realmente nuestro usuario al ingresar al &lt;em&gt;home&lt;/em&gt; necesita que le carguemos los archivos javascript y css de &lt;em&gt;articles&lt;/em&gt; y &lt;em&gt;contact&lt;/em&gt;? debemos recordar que estos archivos afectan el rendimiento de nuestra aplicación y para lograr una optimización en este sentido se vuelve necesario evitar cargarle al usuario archivos innecesarios que afecten su experiencia.&lt;/p&gt;

&lt;p&gt;Con base a lo anterior React nos provee una herramienta llamada &lt;em&gt;Lazy&lt;/em&gt; que nos permite cargar los componentes solamente cuando el usuario los solicita después de la interacción con algún botón o acción que desate dicha solicitud.&lt;/p&gt;

&lt;p&gt;Para implementar un &lt;em&gt;lazy loading&lt;/em&gt; en nuestra aplicación debemos:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Importar la función Lazy desde React y pasarle mediante un callback los imports de cada unos de los componentes que queremos cargar dinámicamente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgehjt9g8uzb7ufr2b1m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgehjt9g8uzb7ufr2b1m.png" alt="Image description" width="527" height="385"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Es importante recordar que los exports desde cada uno de nuestros componentes deben ser por default &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Si intentamos usar nuestra aplicación en este momento observaremos que al navegar entre las rutas la consola nos arrojará el siguiente error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq50nm76dy4cj06rjigs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq50nm76dy4cj06rjigs.png" alt="Image description" width="601" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para solucionar esto debemos realizar el siguiente paso:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Debemos importar un feature que nos provee React llamado Suspense el cual debe envolver nuestras rutas y a su vez permitirnos usar un fallback que se renderizará mientras nuestra aplicación realiza el fetching pertinente de archivos para poder renderizar el componente solicitado por el usuario en la vista. Nuestro código debe lucir así:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhia0wssd3omp8kkdf5pa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhia0wssd3omp8kkdf5pa.png" alt="Image description" width="509" height="427"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Si deseas aprender un poco más a profundidad sobre &lt;a href="https://es.react.dev/reference/react/Suspense" rel="noopener noreferrer"&gt;Suspense&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y eso es todo! &lt;/p&gt;

&lt;p&gt;Debemos recordar que el lazy loading nos permite mejorar el performance de nuestras aplicaciones cuando estas tienen muchos componentes y no queremos cargarle al usuario cientos de archivos Javascript cuando realmente solo necesita unos cuantos para poder acceder a una vista en concreto.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>reactjsdevelopment</category>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Imports absolutos con React y Vite 🚀</title>
      <dc:creator>Jean Carlo Vittory Laguna</dc:creator>
      <pubDate>Sat, 01 Apr 2023 20:01:34 +0000</pubDate>
      <link>https://dev.to/jeanvittory/imports-absolutos-con-react-y-vite-20o7</link>
      <guid>https://dev.to/jeanvittory/imports-absolutos-con-react-y-vite-20o7</guid>
      <description>&lt;p&gt;Por lo general cuando nuestros proyectos en React empiezan a crecer nuestra estructura de carpetas empieza a ganar más anidamientos e importar archivos que se encuentran en otros directorios se torna así: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqarsyi3du5jbmkr6rtr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqarsyi3du5jbmkr6rtr6.png" alt="ruta anidada" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esto hace que nuestro bloque de importaciones sea engorroso de leer y, si necesitamos cambiar alguna carpeta de lugar, se vuelve un dolor de cabeza ya que debemos analizar toda la ruta para evitar errores en la nueva importación.&lt;/p&gt;

&lt;p&gt;Ahora bien, antes de entrar con la solución a este problema es importante entender qué es un import absoluto y qué es un import relativo.&lt;/p&gt;

&lt;h2&gt;
  
  
  IMPORT RELATIVO
&lt;/h2&gt;

&lt;p&gt;Las importaciones relativas nos permiten traer archivos teniendo como punto de referencia la estructura propia de nuestro proyecto. Es decir, podemos importar archivos utilizando como punto de partida el archivo que define el import y navegar hacia el archivo objetivo por medio de &lt;code&gt;../&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  IMPORT ABSOLUTO
&lt;/h2&gt;

&lt;p&gt;Las importaciones absolutas nos permiten ya no tener como punto de partida el mismo archivo que necesita realizar la importación y, de esta forma, nosotros mismo navegar desde él hacia el archivo que deseamos importar sino que ahora utilizaremos una ruta raíz y desde ahí definiremos la ruta concreta a nuestro archivo objetivo, es decir, ya no navegamos desde el archivo que realizará el import hacia el archivo a importar sino que navegamos desde una ruta raíz definida hacia el archivo objetivo.&lt;/p&gt;

&lt;h2&gt;
  
  
  IMPORTS ABSOLUTOS EN REACT Y VITE
&lt;/h2&gt;

&lt;p&gt;Cuando nuestro proyecto empieza a crecer es normal encontrarnos con estructuras de carpetas como esta: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlw465j7k5n3aeqp227g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqlw465j7k5n3aeqp227g.png" alt="estructura de carpetas" width="226" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¿Qué sucede cuando desde alguna página queremos acceder a un componente dentro de la carpeta components? si utilizáramos imports relativos caeríamos en el problema de tener un fila inmensa de &lt;code&gt;../../../../&lt;/code&gt; hasta poder llegar a nuestro componente deseado. Debemos ahora cambiar esto a imports absolutos y para ello Vite nos provee una forma sencilla de hacerlo.&lt;/p&gt;

&lt;h2&gt;
  
  
  CONFIGURANDO VITE
&lt;/h2&gt;

&lt;p&gt;Cuando generamos nuestro proyecto con vite se nos genera un archivo en la raíz llamado &lt;code&gt;vite.config.js&lt;/code&gt; que luce así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6vbmz09zbtx7wzy463s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff6vbmz09zbtx7wzy463s.png" alt="vite.config.js" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En este archivo debemos configurar nuestras rutas absolutas de esta forma:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpphhc52cynj7bbez0scn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpphhc52cynj7bbez0scn.png" alt="vite.config.js configurado" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Debemos usar la propiedad resolve para agregar un alias el cual es un array de objetos en donde cada objeto es una carpeta a la cual deseamos acceder desde cualquier parte de nuestro proyecto y no queremos utilizar un excesivo &lt;code&gt;../&lt;/code&gt; para poder llegar a ella. &lt;/p&gt;

&lt;p&gt;Dentro de cada objeto encontramos dos propiedades &lt;code&gt;find:&lt;/code&gt; y &lt;code&gt;replacement:&lt;/code&gt;. El primero es un string el cual nos permitirá nombrar ese alias para poder usarlo dentro de cada archivo que necesite usar la ruta absoluta. En nuestro caso usamos el @ cómo prefijo al nombre de la carpeta que queremos darle acceso absoluto desde cualquier parte de nuestro proyecto.&lt;/p&gt;

&lt;p&gt;El segundo parámetro &lt;code&gt;replacement:&lt;/code&gt; es la ruta desde &lt;code&gt;vite.config.js&lt;/code&gt; la cual el alias representará. En nuestro caso recomendamos utilizar el modulo &lt;code&gt;path&lt;/code&gt; y &lt;code&gt;__dirname&lt;/code&gt; para normalizar este acceso desde la ruta raíz de cada maquina que ejecute nuestro programa. &lt;/p&gt;

&lt;p&gt;Dicho esto nuestros imports pasarían de lucir así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3r42cdbkexjfru96k5q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3r42cdbkexjfru96k5q0.png" alt="import relativos" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;y ahora lucirían así:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9h3zt8wn61hnok6bfjn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9h3zt8wn61hnok6bfjn.png" alt="Import absolutos" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Con Typescript
&lt;/h2&gt;

&lt;p&gt;En el caso de utilizar typescript debemos realizar una configuración extra en nuestro archivo &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqlri8mp3foyb8osg648.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqlri8mp3foyb8osg648.png" alt="tsconfig.json" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Es importante resaltar que por cada alias agregado en el archivo &lt;code&gt;vite.config.ts&lt;/code&gt; debemos agregar una propiedad más en &lt;code&gt;paths&lt;/code&gt; dentro de &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Eso es todo. Muchas gracias por leer. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>react</category>
      <category>typescript</category>
      <category>vite</category>
    </item>
  </channel>
</rss>
