npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@contextq/memory-tool

v0.1.0

Published

Anthropic memory tool (memory_20250818) backend adapter that stores Claude's memory files as ContextQ contexts

Readme

@contextq/memory-tool

A backend adapter for Anthropic's memory tool (memory_20250818) that stores every memory file as a ContextQ context, wired up entirely through ContextQ's existing /api/contexts* endpoints -- no schema changes.

Background: docs/product/claude-surfaces-positioning.md (Q1) is the feasibility study this package implements; this README covers install/use. Product framing (why an adapter, not a competing surface): the same doc's "Q3 -- The one-page positioning statement".

Install

npm install @contextq/memory-tool @anthropic-ai/sdk

@anthropic-ai/sdk is a peer dependency -- bring your own version (tested against ^0.120.0, the version current when this package was built).

Quick start

import Anthropic from '@anthropic-ai/sdk';
import { contextQMemoryTool } from '@contextq/memory-tool';

const client = new Anthropic(); // reads ANTHROPIC_API_KEY
const memory = contextQMemoryTool({
  apiKey: process.env.CONTEXT_API_KEY!, // sk_live_<40 hex>
  baseUrl: process.env.CONTEXT_API_URL, // default http://localhost:38200
});

const runner = client.beta.messages.toolRunner({
  model: 'claude-opus-5',
  max_tokens: 16000,
  tools: [memory],
  messages: [
    { role: 'user', content: 'Remember that I prefer TypeScript, under /memories/acme/.' },
  ],
});

for await (const message of runner) {
  console.log(message);
}

contextQMemoryTool(options) is a thin factory: new ContextQMemoryTool(options) handed to Anthropic's betaMemoryTool() helper. Use ContextQMemoryTool directly if you want the MemoryToolHandlers-shaped object without the BetaRunnableTool wrapper (e.g. to compose your own tool-loop).

Options

| Option | Default | Notes | |---|---|---| | apiKey | process.env.CONTEXT_API_KEY | ContextQ API key, sk_live_<40 hex>. Required (one of option or env). | | baseUrl | process.env.CONTEXT_API_URL || http://localhost:38200 | ContextQ API base URL. | | contextType | 'reference' | The ContextQ type every memory-tool row gets. The memory tool has no type taxonomy of its own. | | tag | 'mem-tool' | Reserved tag stamped on every row this adapter creates; also used to scope directory/prefix listings so this adapter never lists a caller's unrelated contexts. | | maxRetries, timeoutMs, fetchImpl | see HttpClientOptions | Passed straight through to the internal HTTP client. |

Path model

/memories                          -> lists workspaces
/memories/<workspace>               -> lists that workspace's mem-tool-tagged contexts
/memories/<workspace>/a/b/c.md      -> a single context

The first path segment after /memories/ is the ContextQ workspace slug (lowercased). Everything after it is joined into ContextQ's external_id for that context, namespaced as mem-tool:<workspace>/<rest> -- the workspace is included in the key (unlike the bare "remaining path" sketch in the feasibility doc) because contexts.external_id is unique per tenant, not per (tenant, workspace); two workspaces both wanting a file named notes.md would otherwise collide.

Command -> endpoint mapping

| Command | Endpoint(s) | Atomic? | |---|---|---| | view (file) | GET /api/contexts/by-external/:externalId | yes | | view (directory / /memories root) | GET /api/contexts?workspace=&tag=mem-tool / GET /api/workspaces | yes (read-only) | | create | POST /api/contexts/bulk-upsert (idempotent upsert on externalId) | yes | | delete (single file) | GET .../by-external/:id then DELETE /api/contexts/:id | yes | | delete (workspace root / prefix) | list + loop DELETE /api/contexts/:id | no -- see below | | str_replace | GET .../by-external/:id -> mutate string in-process -> PUT /api/contexts/:id | no -- see below | | insert | same as str_replace | no -- see below | | rename | GET/POST bulk-upsert (create at new path) then DELETE (old path) | no -- see below |

Non-atomic commands, and why

str_replace and insert fetch the whole context, mutate the string in this process, then PUT the whole content back. ContextQ has no line-indexed edit primitive and no server-side conflict detection -- a concurrent writer on the same context can race with this adapter and one write will silently clobber the other.

delete on a workspace root or a directory-shaped prefix loops individual DELETE calls (ContextQ has no bulk-delete-by-path-prefix endpoint). A failure partway through a multi-file delete leaves the remaining files undeleted, and the thrown error surfaces that rather than swallowing it.

rename is best-effort create-then-delete: it creates a new context at the destination path, then deletes the source. If the create step fails, nothing changes (the source is untouched -- a safe failure). If the create succeeds but the delete step then fails, both rows persist and ContextQMemoryTool.rename() throws an error that says so explicitly, naming both paths and the source row's id, rather than hiding the duplication. There is no server-side transaction spanning both calls.

This matches docs/product/claude-surfaces-positioning.md Q1's verdict: "buildable thin, with named gaps" -- three commands (view-whole-file, create, delete-single) map cleanly onto existing endpoints; the other three degrade to adapter-side string surgery with no atomicity guarantee.

Directory listings are not a true nested tree

The reference view command on a real filesystem lists up to two directory levels deep, with true directory objects and byte sizes. ContextQ has no directory object -- view on /memories/<workspace> or a sub-path lists every mem-tool-tagged context whose stored path starts with that prefix, flat, at its full relative path (not a nested tree), and the "size" column shows each row's updatedAt timestamp rather than a byte count (a ContextSummary never carries content, so a real byte size would need an extra fetch per row). Pagination caps at one page of 500 rows.

Context-editing eviction warning

Anthropic's context-editing feature (clear_tool_uses_20250919) can clear old tool results -- including this memory tool's own results -- from a long conversation once a token threshold fires. There is no automatic exemption for the memory tool; protection requires the calling application to explicitly set:

context_management: {
  edits: [{
    type: 'clear_tool_uses_20250919',
    trigger: { type: 'input_tokens', value: 30_000 },
    exclude_tools: ['memory'], // <- must be set by the caller
  }],
}

If a tool named memory is not in exclude_tools, its results (and therefore whatever the model most recently read back from ContextQ) can be pruned from context like any other stale tool result. See docs/product/claude-surfaces-positioning.md Q2 for the full writeup, including why ContextQ's own brain-state injection has the same exposure today and how registering as the literal memory tool (this package's purpose) is the one path that benefits automatically from callers who follow Anthropic's own documented exclude_tools: ["memory"] pattern.

Anthropic SDK shape

This package implements MemoryToolHandlers (@anthropic-ai/sdk/helpers/beta/memory) -- the same interface Anthropic's own reference implementation, BetaLocalFilesystemMemoryTool, implements. The TypeScript SDK does not export a class literally named BetaAbstractMemoryTool to subclass; that shape exists in Anthropic's Python SDK. TypeScript's idiomatic equivalent -- confirmed against the live anthropic-sdk-typescript source, not assumed -- is exactly this: implement MemoryToolHandlers and hand the instance to betaMemoryTool(), which returns a BetaRunnableTool usable with client.beta.messages.toolRunner(...).

Development

npm install
npm test          # unit tests against a local fake HTTP server (node:http) -- no real ContextQ needed
npx tsc --noEmit   # typecheck (src + scripts)
npm run build      # emit dist/ (src only, tests excluded)

Live e2e

scripts/e2e-live.ts runs a real Claude Messages API conversation against a real local ContextQ instance: Claude saves a fact via memory.create, a second, separate turn asks it to recall the fact via memory.view, and the script independently verifies via GET /api/contexts that the value actually landed in ContextQ (not just that Claude said it remembered).

Requires:

  • ANTHROPIC_API_KEY in the environment, valid for api.anthropic.com directly. If ANTHROPIC_BASE_URL is also set (e.g. by a repo-wide .env.local pointing dev tooling at a gateway), the SDK honours it, and a gateway that needs its own separate credential answers a bare 401 Unauthorized that looks exactly like a bad key. The script logs the resolved host (anthropic base url: ...) on every run so this is never silent -- unset it for a direct run: env -u ANTHROPIC_BASE_URL.
  • The local ContextQ dev stack already running and reachable (default http://localhost:38200).
  • Docker access to the shared-context-postgres container (used to create a throwaway tenant + owner user and mint an API key for them by direct INSERT -- see below).
( set -a; source path/to/.env.local; set +a
  env -u ANTHROPIC_BASE_URL CONTEXT_API_URL=http://localhost:38200 npm run e2e:live )

Why a throwaway tenant, not the shared local test tenant

Earlier versions of this script logged in as the repo's shared [email protected] fixture user. That tenant already holds 501+ contexts against PLANS.free.quotas.contexts = 100 (src/billing/plans.ts), so create reliably 402s with a quota error there -- not an adapter bug, just no headroom on shared dev state other sessions have piled onto. The script now creates a fully throwaway tenant + owner user by direct INSERT (the same sanctioned local-dev pattern used for the API key -- see the script's doc comments), mints one API key scoped read+write+admin for that tenant (the admin scope lets the same key drive workspace create/archive/delete too, so no separate JWT/cookie session is needed anywhere in the script), and runs the whole conversation against a tenant that starts at 0 contexts. The login-as-[email protected] path has been removed entirely.

Cleanup runs in the finally block regardless of pass/fail, in order: delete the created context(s) and the workspace via the API, then the API key row, then the user row, then -- conditionally -- the tenant row. The tenant delete is conditional: activity_logs' tamper-evident hash chain is global (not per-tenant), so a tenant that already produced a chained audit row (prev_hash IS NOT NULL) during the run cannot be hard-deleted without forking that chain for every row inserted after it. The script checks for this (mirroring the guard in src/test-helpers/tenant-cleanup.ts) and, when it applies, intentionally leaves the tenant row in place and prints that it did so -- a small, always uniquely-named, low-cardinality leaked row is the accepted trade-off, the same one 50+ of this repo's own test files already make.

If ANTHROPIC_API_KEY is unset, the script prints a message and exits 0 rather than fabricating a result.

What "publishable" means here

This package is built and tested to be published, but publishing itself (npm publish) is not this package's job -- see the repo's task tracker (T666) for who runs it.