Skip to main content

devmemomcp

CI

Memoria de trabajo volátil para desarrollo asistido por LLM, expuesta como servidor MCP. Mantiene en RAM —como grafo con textos vectorizados— todo lo que queda pendiente de codificar entre sesiones, para que el modelo no tenga que recordarlo en contexto ni escribirlo en archivos de notas.

Construido sobre proMCP 0.5.1 (convención triádica can_do / read_* / do_*) con FastMCP como transporte.

pip install -e .
python -m devmemomcp          # servidor stdio

Sobre el BREAKING de proMCP 0.5.0. Renombra TMCPErrorProMCPError y __tmcp_*__promcp_*. No afecta a este servidor: solo consume los decoradores, Registry, ProMCPServer y lint_registry, y ninguno cambió de nombre. Comprobado con la suite completa contra v0.5.1 en un venv limpio.


Por qué volátil

La memoria muere con el proceso, a propósito. No es un registro permanente: es memoria de trabajo. Lo que deba sobrevivir a la sesión se escribe en el repo. Lo que vive aquí es el estado mental de la codificación —qué estabas haciendo, qué te bloqueaba, qué decidiste y por qué— que hoy se pierde entre una sesión de LLM y la siguiente.

Consecuencia de diseño que atraviesa todo el código: una memoria vacía se reporta con quality: degraded, no good. Vacío puede significar "reinició el servidor", y eso no es lo mismo que "no queda trabajo pendiente".


Los 8 tools

Tool Categoría Para qué
can_do precondición ¿es viable esta intención ahora, y qué la bloquea?
read_pending observación qué está abierto, ordenado por énfasis
read_related observación qué sé ya sobre este tema (vectores + grafo)
read_item observación un item con vecinos, bloqueadores e historial
do_remember mutación grabar un pendiente y auto-enlazarlo
do_update_item mutación cambiar estado/prioridad/texto, reforzar énfasis
do_link mutación declarar dependencias y bloqueos entre items
do_forget mutación borrar un item de verdad (no solo archivarlo)

Los ocho que proMCP recomienda como máximo por servidor. do_forget ocupa el slot que se dejó libre a propósito en la primera versión de este servidor — ver FUN-1 en TODO.md para el porqué de esa elección sobre las otras candidatas.

Protocolo de sesión

1. Al abrir la sesión          → read_pending      recupera el énfasis anterior
2. Antes de codificar algo     → read_related      decisiones y bloqueos del tema
3. Al detectar un pendiente    → do_remember       grábalo en cuanto aparezca
4. Al empezar / terminar       → do_update_item    status=active / status=done
5. Antes de cerrar algo dudoso → can_do            ¿quedan bloqueadores?

Arquitectura

Outside-in, con el transporte en la frontera exterior. Cada capa solo conoce a la de adentro:

devmemomcp/
├── domain/          invariantes puras · no sabe que MCP existe
│   ├── models.py      MemoryNode, Edge, salience
│   ├── embedding.py   Embedder (Protocol) + HashingEmbedder
│   ├── graph.py       grafo dirigido en RAM, difusión por saltos
│   ├── store.py       casos de uso, RLock, idempotencia
│   └── errors.py
├── surface/         superficie triádica · conoce proMCP, no FastMCP
│   ├── reads.py       read_pending, read_related, read_item
│   ├── mutations.py   do_remember, do_update_item, do_link
│   └── feasibility.py can_do
└── transport/       única frontera con FastMCP
    └── server.py      create_server()

transport/server.py es el único archivo que menciona el transporte. Cambiar de FastMCP a otra cosa toca un archivo, no cuarenta.

El grafo

Nodos de seis tipos (task, blocker, decision, question, insight, artifact) y aristas dirigidas de seis tipos (blocks, depends_on, subtask_of, relates_to, supersedes, touches). Las adyacencias se mantienen en espejo (entrantes y salientes) para que recorrer el vecindario sea O(grado); al leer hacia atrás se reporta la inversa semánticablocks se lee como blocked_by— para que el modelo entienda la relación tal como aplica al nodo que consultó.

Los vectores

HashingEmbedder por defecto: hashing vectorizer con unigramas y bigramas, tf sublineal y normalización L2. Sin modelo, sin red, sin descarga, determinista entre procesos. Vectores dispersos (dict[int, float]) porque un título produce ~20 dimensiones no nulas de 4096, y el coseno disperso es O(min(|a|,|b|)) en Python puro.

No entiende sinónimos. Para eso hay un embedder denso opcional (pip install devmemomcp[semantic]), o se implementa el Protocol Embedder propio y se pasa al store:

from devmemomcp.domain.dense_embedder import SentenceTransformerEmbedder

create_server(embedder=SentenceTransformerEmbedder())

Nada más en el codebase cambia. Umbral de auto-enlace por embedder, no global: AUTOLINK_THRESHOLD = 0.15 está afinado para el embedder léxico (pares relacionados 0.17–0.59, no relacionados exactamente 0.0). Un modelo denso da coseno > 0 a casi todo par, así que ese mismo umbral saturaría el grafo de aristas falsas. Por eso cada Embedder declara su propio autolink_threshold (HashingEmbedder usa 0.15, SentenceTransformerEmbedder usa 0.35 — ver el docstring de devmemomcp/domain/dense_embedder.py para cómo se calibró ese 0.35) y MemoryStore lo toma como default; se puede seguir forzando uno explícito con MemoryStore(embedder=..., autolink_threshold=...).

El énfasis (salience)

Es lo que hace que la memoria "mantenga en énfasis" lo pendiente en lugar de devolver una lista plana:

salience = peso_estado × (0.45·prioridad + 0.35·recencia + 0.20·centralidad + boost)

La recencia decae exponencialmente (vida media 24 h por defecto) desde el último toque: lo que nadie mira se apaga solo. La centralidad hace subir a los items muy conectados. done y dropped colapsan a 0 — siguen en el grafo como contexto histórico, pero dejan de competir por atención. bump=true refuerza manualmente (+0.1, tope 0.5).

Los tres canales de can_do

proMCP separa desde 0.4.0 tres respuestas que antes se mezclaban, y la distinción no es cosmética:

Canal Significa Ejemplo aquí
candidates[] hay ruta y es transitable "puedes grabar el pendiente"
blocked[] la ruta existe y no es transitable ahora cerrar un item con bloqueadores abiertos
unroutable[] no hay ruta al resultado — no hay capacidad que nombrar el item que citas ya no existe

Antes de adoptarlo, este servidor cometía los dos abusos que el spec nombra: metía un item inexistente en blocked[] —inventando una ruta viva— y bloqueaba los read_* con la memoria vacía, cuando esas lecturas son transitables (devuelven cero items con quality: degraded, que ya es la señal correcta). La memoria fría es ahora contexto, no un bloqueo.

Los tres motivos de unroutable están cubiertos: requirements_unmet (el item no existe), malformed_request (el intent no declara nada que enrutar — antes devolvía las seis capacidades con confianza 0.5, ruido disfrazado de respuesta) y no_producer (se pidió algo que este servidor no hace, que antes salía como un feasible: false sin explicar).

Invariante que impone el builder: unroutable[] no vacío obliga a feasible: false. Por eso, cuando el resultado pedido es inalcanzable no se emite ningún candidato — ofrecer do_remember a quien preguntó "¿puedo cerrar el item X?" es ruido, no ayuda.

Recuperación híbrida

read_related no es solo búsqueda vectorial. Vectoriza la consulta, saca semillas por coseno, y las difunde por el grafo con decaimiento por salto. Un nodo alcanzable por varios caminos se queda con el mejor, no con la suma — sumar premiaría a los hubs por ser hubs, no por ser relevantes. El puntaje final mezcla semántica (0.55), proximidad en el grafo (0.25) y salience (0.20). Así aparece lo que está conectado a lo que estás mirando aunque su texto no se parezca en nada.


Decisiones que conviene no re-descubrir

Las lecturas no mutan nada. Ni touched_at, ni contadores de acceso. Es lo que hace que los read_* sean paralelizables y cacheables de verdad, como promete proMCP. Refrescar la atención es un acto explícito: do_update_item(bump=true). Hay un test que lo vigila.

El store serializa con RLock. FastMCP 3.x corre los tools en hilos de worker (run_in_thread=True por defecto), así que el estado en RAM se toca concurrentemente. El lock vive en MemoryStore y ninguna capa de arriba se entera.

Nada de from __future__ import annotations en surface/ (vivo: el fix está propuesto pero no ha entrado en 0.4.0, 0.4.1, 0.5.0 ni 0.5.1 — ver promcp-pr/). Bajo PEP 563 las anotaciones quedan como strings y promcp._infer_input_schema —que compara contra los tipos reales— emite un inputSchema vacío, lo que hace fallar al propio linter de proMCP (I002/I003). FastMCP resuelve los strings por su cuenta, así que el síntoma solo aparece del lado del registry y es fácil de perseguir.

valid_until por bandas, no por constante plana (§11.3). Enrutar una intención genérica depende solo del catálogo, que no cambia → banda estática, 240 s. Responder sobre un item concreto depende de un estado que cualquier do_* invalida → banda dinámica, 20 s.

Las descripciones de los do_* explican cómo construir el idempotency_key (AP-9). Importa especialmente aquí: el store deduplica por clave durante 24 h, así que una clave derivada del tipo de operación en vez de la instancia haría que el segundo do_remember devolviera el nodo del primero y nunca grabara nada — sin error visible, con el servidor comportándose correctamente.

Registry explícito, no el singleton de módulo. Permite dos servidores en el mismo proceso (tests, multi-tenant) sin disparar CanDoSingletonError.

Los do_* fallidos devuelven un compensation_hint no nulo. proMCP exige el campo cuando compensable=True, incluso en el camino de error; devolver None dispara un ContractViolation que enmascara el error real.


Verificación

pip install -e ".[dev]"

pytest -q                                              # tests: dominio, contratos, linter y transporte real
pytest --cov=devmemomcp --cov-report=term-missing -q   # cobertura, umbral 85% (CAL-7)
ruff format --check devmemomcp tests                   # formato (CAL-6)
ruff check devmemomcp tests                             # lint (CAL-6)
mypy --strict devmemomcp                                # tipos (CAL-5)

El servidor pasa el linter de proMCP 0.5.1 con 0 errores y 0 warnings, y hay tests que lo comprueban en CI en lugar de confiar en que siga siendo cierto. El test end-to-end recorre el patrón canónico completo — can_doread_*do_*read_* — incluyendo que can_do impida cerrar un item que todavía tiene bloqueadores abiertos.

tests/test_stdio.py (CAL-8) es el único test que no usa el cliente in-memory de FastMCP: lanza python -m devmemomcp como proceso real, habla MCP por stdin/stdout con el SDK cliente oficial (mcp), completa el handshake y ejecuta un do_remember + read_pending de verdad — la única forma de detectar algo que escriba basura en stdout y corrompa el protocolo.

Los cuatro pasos de arriba, más promcp-lint sobre el registry en tiempo de ejecución, corren en CI (.github/workflows/ci.yml) en cada push y PR sobre Python 3.10, 3.11 y 3.12 — ver el badge al principio de este README.


Configuración en un cliente MCP

Instalación previa en el entorno donde vive el intérprete que se invoque abajo (una vez por proyecto, o global si prefieres tenerlo siempre disponible):

pip install -e /ruta/a/devmemomcp   # o: pip install -e . estando dentro del repo

El bloque de configuración es el mismo para cualquier cliente MCP por stdio — lo que cambia es dónde se pega. command/args es literalmente python -m devmemomcp, tal como lo arranca CAL-8 en el test de proceso real:

{
  "mcpServers": {
    "devmemomcp": {
      "command": "python",
      "args": ["-m", "devmemomcp"]
    }
  }
}

Claude Code

Verificado en este mismo entorno (Windows, Claude Code 2.1.234) con el comando real, no de memoria:

claude mcp add devmemomcp -s local -- python -m devmemomcp
  • -s local lo registra solo para este proyecto (el scope por defecto); usa -s user para tenerlo disponible en todos tus proyectos, o -s project para versionarlo en .mcp.json y compartirlo con quien clone el repo.
  • Todo lo que va después de -- es el comando literal que Claude Code ejecuta por stdio: exactamente python -m devmemomcp.

Comprobado con claude mcp get devmemomcp inmediatamente después de añadirlo:

devmemomcp:
  Scope: Local config (private to you in this project)
  Status: ✔ Connected
  Type: stdio
  Command: python
  Args: -m devmemomcp

claude mcp list lo muestra ✔ Connected junto al resto de servidores MCP configurados, confirmando el handshake — el mismo handshake que tests/test_stdio.py (CAL-8) automatiza contra un proceso real. Tras verificarlo se retiró con claude mcp remove devmemomcp -s local para no dejar un servidor de más en la configuración de quien corrió esta verificación; repite el add de arriba para instalarlo de verdad.

Para confirmar que salen los 8 tools dentro de una sesión de Claude Code, usa /mcp (o pregúntale a Claude "qué tools expone devmemomcp") una vez conectado.

Claude Desktop

No verificado en este entorno — no hay forma de abrir Claude Desktop (aplicación de escritorio) desde una sesión de Claude Code headless en esta máquina, así que esto son instrucciones de edición de archivo, no una captura de pantalla ni una sesión probada:

  1. Localiza claude_desktop_config.json:

    • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows: %APPDATA%\Claude\claude_desktop_config.json
  2. Añade la entrada devmemomcp dentro de mcpServers (créalo si el archivo está vacío o no existe todavía):

    {
      "mcpServers": {
        "devmemomcp": {
          "command": "python",
          "args": ["-m", "devmemomcp"]
        }
      }
    }
    
  3. Reinicia Claude Desktop por completo (no basta con cerrar la ventana).

  4. Los 8 tools deberían aparecer bajo el icono de herramientas (🔨) de la conversación, con el prefijo devmemomcp delante de cada nombre.

Si python no resuelve al intérprete correcto (varias instalaciones, un venv específico), sustituye "command": "python" por la ruta absoluta al intérprete donde se instaló el paquete — Claude Desktop no hereda el PATH de una shell interactiva en todos los sistemas.


Decisiones de diseño

Elecciones que condicionan el diseño y que no conviene reabrir sin un motivo nuevo. Cada una fue evaluada, no asumida.

¿Persistencia? No, por defecto — y tampoco un export/import a mano

La memoria es volátil por diseño, no por descuido: muere con el proceso a propósito, y esa decisión atraviesa el código entero, desde el RLock en MemoryStore hasta el quality: degraded que reporta una memoria vacía (que puede significar "reinició el servidor", no "no queda trabajo pendiente"). Se evaluó el punto intermedio obvio — un export/import explícito, invocado a mano por el usuario, nunca autoguardado — y se descarta también, no solo el autoguardado:

  • No resuelve ningún problema que no resuelva ya el repo. Lo que debe sobrevivir a la sesión se escribe donde ya vive el resto del proyecto: un commit, un TODO.md, un comentario. Un export en un formato propio de devmemomcp es una copia paralela de eso, no una mejora sobre eso.
  • Cambia lo que es este proyecto. En cuanto existe un archivo que un usuario puede reabrir la semana siguiente, deja de ser memoria de trabajo y pasa a ser una base de datos de tareas con pasos extra — justo lo que el diseño evita a propósito (ver "Por qué volátil" arriba).
  • No es gratis mantenerlo. Un formato de export es superficie a versionar y a la que hay que darle compatibilidad hacia atrás, por una función que nadie pidió resolviendo un problema que no existe: si un LLM necesita que algo persista, ya tiene un tool para eso — escribir en el repo.

Cerrado: no se añade ni autoguardado ni export/import manual. Si esto se replantea, que sea porque cambió el problema (por ejemplo, sesiones que necesitan sobrevivir a un crash, no a un cierre limpio), no porque "persistir siempre es mejor".

El peso del body en el vector: se truncó, un cambio de una línea

searchable_text() repetía el título para pesar el doble y concatenaba el body completo (hasta MAX_BODY_BYTES, 8 KB). Con un body largo esa repetición no basta: unos pocos miles de caracteres de prosa dominan por volumen a un título de un puñado de palabras en un hashing vectorizer, que no sabe de posición ni de importancia estructural — solo cuenta n‑gramas.

No hay corpus real en este entorno para medir la mejora de recall empíricamente, así que la decisión se tomó por el argumento de diseño, no por benchmark, y se dejó lo más barata y reversible posible:

  • Se truncó el body a SEARCHABLE_BODY_CHARS (500 caracteres) solo para el texto que ve el embedder. El body almacenado y el que devuelven read_item/read_pending/read_related no cambian ni un carácter — el corte vive únicamente dentro de MemoryNode.searchable_text().
  • Se truncó por caracteres al principio, no por una ventana ni un resumen, porque el principio de un body suele concentrar la señal (qué es, por qué importa) y truncar es una operación sin estado, determinista y de coste cero — cualquier alternativa (TF‑IDF sobre el body para quedarse con los términos más informativos, un resumen) es más código para un problema que 500 caracteres ya mitiga.
  • 500 es una elección razonada, no calibrada: dos o tres frases suelen bastar para que el hashing vectorizer capture el tema; comparado con un título de ≤200 caracteres repetido dos veces (~400 caracteres de peso), deja al body con un peso comparable al del título en vez de aplastarlo por longitud. No está medido contra un corpus real — si se observa en uso que 500 es demasiado poco (temas que solo se distinguen entrada varios párrafos) o demasiado (ruido que sigue colando temas ajenos), es el primer número a mover, y un buen candidato para volver a medir cuando FUN-3 traiga un embedder denso con semántica distinta a la de n‑gramas.
  • Alternativa considerada y descartada por ahora: ponderar el body aparte en vez de truncarlo (dos vectores, uno de título y otro de body, combinados con pesos distintos en el coseno). Es estrictamente más flexible, pero es más superficie — dos vectores por nodo, una fórmula de combinación que calibrar — para un problema que truncar ya resuelve razonablemente. Si el truncado demuestra ser insuficiente, esa es la vía natural de seguir.

Test: test_searchable_text_trunca_el_body_pero_to_dict_lo_devuelve_completo en tests/test_domain.py.

El grafo como recurso MCP (devmemo://graph): no, por ahora

MCP separa tools de resources. Un recurso devmemo://graph leería el grafo (o un digest) sin gastar una llamada a tool, y en abstracto encaja: el grafo es un dato que un cliente podría querer leer directamente. Se descarta por ahora, no por principio:

  • No hay ningún cliente MCP en uso que consuma resources frente a este servidor — es superficie sin consumidor. Construir un recurso que nadie lee no valida nada; valida que el código compila.
  • Ya existe una vía de lectura completa y sin efectos secundarios: read_pending, read_related y read_item cubren "todo lo abierto", "lo relacionado con X" y "un nodo con su vecindario" respectivamente. Un recurso devmemo://graph sería un cuarto camino a la misma información, con su propio formato de serialización que mantener sincronizado con los otros tres.
  • Añade una segunda superficie a la que transport/server.py — hoy el único archivo que menciona FastMCP — tendría que declarar y versionar junto a los 8 tools, por un ahorro (una llamada a tool menos) que no se ha demostrado que importe: todas las lecturas hoy están medidas en milisegundos incluso a 2000 nodos (ver TODO.md, bloque ESC).

Cerrado. Se reabre el día que exista un cliente MCP real hablando con este servidor que sí distinga resources de tools en su UX y para el que ese ahorro de una llamada importe.

Multi-proyecto: (a) un servidor por proyecto, no un campo project

Hoy un servidor MCP = una memoria. Con tres repos abiertos, la alternativa a levantar tres servidores sería (b) un campo project en cada nodo y filtrado en todos los read_*. Se elige (a):

  • El aislamiento por proceso ya es completo y gratis: cada servidor tiene su propio MemoryStore, su propio grafo, su propio índice de vectores. No hay ningún caso en que la memoria de un proyecto se filtre a otro, porque no comparten proceso, y eso vale sin escribir una línea.
  • (b) no compra aislamiento — ya existe — compra la posibilidad de ver varios proyectos a la vez desde un único servidor. Nadie ha pedido eso, y encaja mal con lo que ya existe: session ya distingue "sesión de trabajo dentro de un proyecto"; añadir project como una segunda dimensión de filtrado obligaría a tocar los cuatro read_* y can_do (que agrega sobre el store entero en su context) para que ninguno se salte el filtro — el mismo tipo de superficie nueva que sí importaría vigilar con tests, por un caso de uso hipotético.
  • Los clientes MCP (Claude Desktop, Claude Code) ya soportan de forma nativa varios servidores declarados a la vez con nombres distintos — es exactamente el mecanismo de (a), no algo que este proyecto tenga que inventar.

Aislamiento por proceso basta; no se justifica (b). Documentado como la forma correcta de usarlo: un devmemomcp por proyecto, cada uno con su propia entrada en la configuración del cliente MCP (ver "Configuración en un cliente MCP" arriba), con un command/args/cwd distinto por repo si se necesita que cada proceso arranque en su propio directorio.

start_* + read_status_* para operaciones largas: no aplica hoy

Es el patrón de proMCP para mutaciones que no terminan en la misma llamada. Hoy no aplica: con HashingEmbedder, todo — vectorizar, auto-enlazar, recorrer el grafo — está medido en milisegundos incluso a 2000 nodos (ver TODO.md, bloque ESC). Introducir start_do_remember + read_status_* para una operación de 1–3 ms sería puro ceremonial: dos tools más ocupando el límite de 8, y un cliente que tendría que hacer polling de algo que ya terminó cuando le llega la respuesta del start_*.

Condición de reapertura, ya anotada: si FUN-3 (embedder denso opcional) llega a producción, vectorizar puede pasar de milisegundos a segundos por llamada a un modelo. Ese es el momento de medir de nuevo do_remember con el embedder denso puesto y decidir si cruza el umbral en el que vale la pena start_*/read_status_* — no antes.

stream_* para observación continua: no

Es el patrón de proMCP para telemetría en vivo — "avísame cuando cambie el énfasis". Se descarta: un LLM conversando por turnos no está escuchando un canal entre turnos; lo primero que hace al volver es leer el estado actual con read_pending/read_related, no reproducir un histórico de eventos que se perdió mientras no había ningún turno en curso. Un stream_* de cambios de salience serviría a un observador humano con un dashboard abierto, que no es el consumidor que tiene este servidor. Cerrado sin condición de reapertura explícita: reabrir esto exigiría que apareciera un consumidor con esa forma de trabajar, no una mejora incremental del servidor actual.

¿Publicar en PyPI? No, todavía — bloqueado por una dependencia externa

Hoy devmemomcp solo se instala desde git (pip install -e . sobre un clon, o pip install git+https://github.com/alexlqi/devmemomcp). Publicarlo en PyPI no es una decisión de diseño de este repo, es un bloqueo técnico:

  • pyproject.toml fija promcp como promcp[transport] @ git+https://github.com/alexlqi/promcp.git@v0.5.1 — una dependencia por URL de git, no por nombre de paquete de índice.
  • PyPI rechaza subir un paquete que declare una dependencia por URL git en [project.dependencies] (lo bloquea twine upload / el propio índice, no es una regla de estilo que se pueda saltar). No hay forma de publicar devmemomcp en PyPI mientras promcp no esté también en PyPI.
  • promcp en sí mismo tampoco está publicado en PyPI hoy — solo existe como repo de GitHub con tags (v0.3.0v0.5.1, ver el historial de este mismo README y CHANGELOG.md).

Decisión: no se persigue publicar en PyPI hasta que promcp se publique primero ahí. Es una dependencia externa bloqueante, no algo que este repo pueda resolver por su cuenta (cambiar a una copia vendorizada de promcp para poder publicar sería peor que no publicar: divergiría en silencio de la fuente real). Si promcp llega a PyPI, esta tarea se reabre reemplazando el @ git+... por un rango de versión normal (promcp[transport]>=0.5.1) y es, a partir de ahí, un python -m build && twine upload estándar — nada más de este repo tiene que cambiar para que sea publicable.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

devmemomcp-0.1.1.tar.gz (82.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

devmemomcp-0.1.1-py3-none-any.whl (63.0 kB view details)

Uploaded Python 3

File details

Details for the file devmemomcp-0.1.1.tar.gz.

File metadata

  • Download URL: devmemomcp-0.1.1.tar.gz
  • Upload date:
  • Size: 82.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for devmemomcp-0.1.1.tar.gz
Algorithm Hash digest
SHA256 17a9ef582c929af0c8cf500f15e70d48863b2a9d33a5b9375b9e4a497fbe69f1
MD5 956df5b6581df38648b521084e068907
BLAKE2b-256 d251ef5655ca26f7ea8c4a2881014c778c0ac7b9f35ce4dd84e3f72f071933da

See more details on using hashes here.

File details

Details for the file devmemomcp-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: devmemomcp-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 63.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.14

File hashes

Hashes for devmemomcp-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 4bd3d202f98d5508241328874ff57d6ad67ccc1478cca8faf3bc2bde22506279
MD5 e3df4e6890aeb9f7d84bcff5b3179eec
BLAKE2b-256 42d14678c3dbe58c1eb641256b7c98aa21776cc4a7e166b892d832693b84665c

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page