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

usemycontext

v1.4.2

Published

The official UseMyContext SDK for the personal context layer for AI - a typed client + React hook for the structurally-blind personal-context MCP. npm i usemycontext, then drop the user's compiled context straight into any AI app.

Readme

usemycontext

MIT license | usemycontext.ai | Docs | GitHub | Claude Code plugin | What is a context layer?

Try it in 30 seconds, no signup. The demo account works two ways: sign in on the web at usemycontext.ai (the "Try the live demo" button), or connect any MCP client to https://mcp.usemycontext.ai/mcp and use it at the OAuth sign-in. Login: [email protected], code 424242 (fixed - no mailbox needed). A shared, read-only account with a full profile, files, and a shared context. Docs: https://usemycontext.ai/docs

The user's context, in any AI app you ship. UseMyContext.ai is the personal context layer for AI: one npm i, one hook, and the user's compiled personal context drops straight into your prompts - while the UseMyContext public surface stays structurally blind to the content (it declares no data bindings, so it holds no readable user data, which lives on an isolated data plane behind it).

The same context your users connect to Claude and ChatGPT, plus coding tools like Gemini CLI and Cursor, is what this SDK reads - one profile, every AI.

Connected AIs are proactive about it: the server's own MCP guidance tells them to check the user's profile and documents before saying they do not know something about the user - so the user should not have to name UseMyContext for their own context to be used. The same guidance closes the loop at the other end: when a conversation has surfaced something new and durable about the user and is wrapping up, the AI is told to offer to save it with suggest_update before the context is lost, as a pending suggestion the user reviews and accepts.

npm i usemycontext
import { useMyContextReact } from "usemycontext/react";

// The user pasted their UseMyContext token (grab one at usemycontext.ai/#/connect).
// This component connects, then drops their compiled context into a prompt.
function Assistant({ token }) {
  const { state, connected, connect, client } = useMyContextReact();

  async function run() {
    await connect({ token });
    const { composite } = await client.profile(); // the user's compiled context
    const { passages } = await client.ask("what should I keep in mind?");
    const systemPrompt =
      `You are helping a specific person. Their context:\n${composite}\n\n` +
      `Relevant notes:\n${passages.map((p) => p.text).join("\n")}`;
    console.log(systemPrompt); // -> hand this to your LLM
  }

  return (
    <button onClick={run} disabled={state === "connecting"}>
      {connected ? "Connected" : "Use my context"}
    </button>
  );
}

That is the whole integration. The hook holds the user's own token and calls the UseMyContext MCP server exactly like Claude does - same trust model, new client shape.

Without React

The core entry is framework-free:

import { useMyContext } from "usemycontext";

const ctx = useMyContext();
await ctx.connect({ token }); // v1 is TOKEN-IN
const { composite } = await ctx.profile();

The drop-in button

import { ConnectMyContext } from "usemycontext/react";

<ConnectMyContext token={token} onConnect={(s) => console.log(s)} />;

It renders one <button class="umc-connect-button" data-umc-state="..."> whose label tracks the connect state. Style it however you like - the markup is yours.

The CLI: npx usemycontext pull

Pull your curated context into a local session-starter file that any AI tool can read at startup:

export UMC_TOKEN=<your token from the Connect panel at usemycontext.ai>
npx usemycontext pull                     # writes ./UMC-CONTEXT.md
npx usemycontext pull --out notes/me.md   # or anywhere you like

The file is your compiled composite - never raw facts, files, or audit - wrapped in a fixed preamble that tells the consuming AI it is human-curated ground truth that must not be rewritten or appended to, plus a date stamp and a pointer back to your live profile. Overwrites are idempotent; any failure exits non-zero with a plain-English message and never leaves a partial file.

The token can also live in a token=<value> line in ~/.usemycontext. It is deliberately never accepted as a CLI flag (flags land in shell history), and the CLI never writes it anywhere. Pulls show up as their own client in your activity feed instead of blending into your other AIs.

Wire it into Claude Code as a SessionStart hook (in .claude/settings.json):

{
  "hooks": {
    "SessionStart": [
      { "hooks": [{ "type": "command", "command": "npx -y usemycontext pull" }] }
    ]
  }
}

For agent frameworks that read a bootstrap file at startup, point the bootstrap list at the pulled UMC-CONTEXT.md. More recipes: https://usemycontext.ai/docs/cli-pull

The tools

The MCP server exposes thirteen tools, and the client gives you an ergonomic wrapper for every one of them. Once connected, call the reads and the one write below - all read-only against your files. One tool, suggest_update, can write: it files a pending suggestion that you review and approve in the web app. Nothing the AI does ever edits your profile or your files directly, and every access leaves a record in your audit trail.

| Method | Returns | What it is | |---|---|---| | client.profile({ projectId?, handle? }) | { composite, facts? } | the compiled personal context - drop into a system prompt | | client.listProfiles() | ProfileListResult | list_profiles - your own profiles (name, @handle, privacy), metadata only | | client.info() | InfoResult | info - static about-the-service metadata, capability boundary, doc links | | client.account() | AccountSummary | account - your current plan, limits, and storage usage (read-only; usage.storageUsedBytes is null when the figure could not be read - show it as unknown, not zero) | | client.ask(query, { k?, projectId?, handle? }) | { passages, text } | ask_docs - cited passages to ground the LLM | | client.queryTable(fileId, query, { projectId?, handle? }) | TableQueryResult | query_table - EXACT counts/sums/filters over one tabular file | | client.listFiles({ projectId?, handle? }) | FileMeta[] | the user's files (metadata only) | | client.searchFiles(query, { projectId?, handle? }) | FileMeta[] | search files by name | | client.getFile(fileId, { projectId?, handle? }) | { text, downloadUrl?, image?, imageBase64?, mimeType? } | the file's full extracted text, or an inlined image | | client.search(query, { projectId?, handle? }) | { results: [{ id, title, url }] } | search - the deep-research ALIAS of search_files (same data, ChatGPT's contract shape) | | client.fetchDoc(id, { projectId?, handle? }) | { id, title, text, url, metadata? } | fetch - the deep-research ALIAS of get_file (full extracted text in ChatGPT's contract shape) | | client.suggestUpdate(text) | { id?, text } | the one write - a PENDING suggestion the user reviews | | client.sharedContext(selector?) | { mode, shares, composite } | read contexts shared with you, opened to the network, or a teammate's |

Files: getFile and images

getFile(fileId) returns a document's full extracted text plus a short-lived downloadUrl:

const { text, downloadUrl } = await client.getFile("resume.pdf");

When the file is an image, the service inlines it and the result carries the base64 bytes so you can render it directly (a text/document result leaves these undefined):

const file = await client.getFile("headshot.png");
if (file.image && file.imageBase64) {
  const dataUrl = `data:${file.mimeType};base64,${file.imageBase64}`;
  // <img src={dataUrl} /> - the picture, not a link
}

An image too large to inline (over the ~4MB guard) falls back to downloadUrl with a short note in text (no imageBase64). getFile also surfaces the parsed name, contentType, and a truncated flag when the text was cut at the service's size guard.

FileMeta (from listFiles / searchFiles) carries the file id, name, size, contentType, modified, and (when known) the owner-side status, a user-chosen displayName, and hasText (whether a processed file yielded searchable text).

Deep-research aliases: search and fetchDoc

The server also exposes search and fetch - thin read-only ALIASES of search_files and get_file in the result shape ChatGPT's deep research requires. Same data, same permissions, same audit trail; only the shape differs. client.search(query) returns { results: [{ id, title, url }] }, and client.fetchDoc(id) returns { id, title, text, url, metadata? } with the file's full extracted text (fetchDoc is named so a destructured method never shadows the global fetch; on the wire it calls the MCP tool named fetch). Prefer searchFiles / getFile in your own code - they carry richer metadata, download links, and inline images.

Tables: queryTable

queryTable(fileId, query) runs an EXACT, deterministic query over ONE tabular file (a CSV, the first table of a spreadsheet/PDF/Word document, or a table read out of an image). Reach for it instead of ask whenever the question needs counting, summing, averaging, min/max, filtering, or exact row lookups over structured data - semantic search undercounts big tables, while this executes over every row and returns exact numbers. The query is a small constrained DSL (TableQuery):

// count the rows whose status is "failed"
const failed = await client.queryTable("f_orders", {
  where: [{ col: "status", op: "eq", value: "failed" }],
  aggregates: [{ fn: "count" }],
});
// -> { columns: ["count"], groups: [{ count: 42 }], rowCountMatched: 42, ... }

// total amount per region
await client.queryTable("f_orders", {
  groupBy: "region",
  aggregates: [{ fn: "sum", col: "amount" }],
});

// return the matching raw rows
await client.queryTable("f_people", {
  select: ["name", "email"],
  where: [{ col: "country", op: "eq", value: "FR" }],
});

op is eq | neq | contains (text, case-insensitive) or gt | gte | lt | lte (numeric); fn is count | sum | avg | min | max (col required except for count); where clauses are AND-combined; limit defaults to 50 (max 200). The result is TableQueryResult - rows (select mode) OR groups (aggregate mode), plus rowCountTotal / rowCountMatched / truncated / tableSource ("csv" | "extracted" | "image"; an image table was read out of a picture, so treat its numbers as approximate). A typed engine refusal (an unknown column - the message lists the file's real columns - a non-tabular file, and so on) throws a ToolCallError.

sharedContext selectors

Call sharedContext() with no argument to LIST the contexts others shared with you (metadata only). To READ one, pass a grantId string (back-compatible) or an options object with exactly one selector:

await client.sharedContext();                       // LIST shares given to you
await client.sharedContext("g_alice");              // READ a share by grantId
await client.sharedContext({ from: "g_alice" });    // same, object form
await client.sharedContext({ handle: "@carol" });   // READ a network-open project by @handle
await client.sharedContext({ org: "o1", member: "m2" }); // READ a teammate's live context
await client.sharedContext({ saved: "carol-notes" });    // READ a context you saved by name

org + member optionally take orgProject to pick which of the teammate's projects to read. When the access behind a selector is gone (revoked, expired, left the team, or an unknown saved name), the result is a clean unavailable read (mode: "read", composite: null) - never a false success.

Structured results

The UseMyContext server declares an MCP output schema on every tool and returns a machine-readable structuredContent object alongside the rendered text. Since v1.1.0 the SDK reads that structured result directly whenever it is present; when structuredContent is absent or malformed, the client falls back to parsing the text exactly as before - older servers and text-only results keep working with no code change on your side.

You do not need to do anything to benefit: the wrappers return the same typed shapes as before, just sourced more reliably. Two small additions ride along: getFile results can now carry content (the file's extracted text alone, without the header and download-link wrapping), and sharedContext reads can carry owner (who the served context belongs to). The structured shapes are exported as types (StructuredProfile, StructuredFileList, StructuredGetFile, StructuredAskDocs, StructuredSuggestUpdate, StructuredSharedContext) if you work with raw results.

Projects (projectId or handle)

profile, ask, queryTable, listFiles, searchFiles, getFile, search, and fetchDoc accept an optional projectId - or, equivalently, the project's own public @handle (with or without the leading @). Both apply only to an "all"-scope connection and are resolved against your own projects by the service; a single-project token ignores them (the token's own project binding wins).

const { composite } = await client.profile({ projectId: "p2" });
const files = await client.listFiles({ projectId: "p2" });

// Same thing, addressed the way a person says it:
const work = await client.profile({ handle: "@my-work" });

handle must name one of your own profiles. A handle belonging to anyone else - or one nobody holds - is refused with the same error either way (so a private handle stays unguessable), and it is never quietly swapped for a different profile of yours. When both selectors are supplied the handle wins. To read a context somebody else shared with you or published, use sharedContext({ handle }), which is a different read entirely.

The connect state machine

ctx.state is one of:

idle -> connecting -> connected
                   \-> declined   (user/host refused)
                   \-> error      (network / transport failure)
connected -> expired              (token rejected mid-session - re-connect)
<any>     -> disconnected         (disconnect() was called)

A 401 from the server (an expired or revoked token) moves the client to expired and throws TokenExpiredError; re-run connect({ token }) with a fresh token to recover. Revoking is enforced on the server: the token dies immediately, everywhere - the user can revoke any connection from the web app's Connect page, and nothing needs cleaning up on your side.

Errors

Every failure is a typed subclass of UseMyContextError:

  • NotConnectedError - a wrapper was called before connect().
  • TokenExpiredError - the server rejected the token (carries the WWW-Authenticate header).
  • ScopeDeniedError - this connection is not authorized for that tool.
  • ToolCallError - the tool returned an error result.
  • TransportError - the network/transport failed.

Token model (v1)

v1 is token-in only: you supply the user's UseMyContext session token to connect({ token }). The full browser-OAuth-in-the-SDK flow (a hosted popup on connect.usemycontext.ai) is v2.

Persistence

Default: the token is kept in memory only and is never written to localStorage unless you opt in. It does not survive a page reload by default.

This is deliberate, but be clear about what it does and does not do. The v1 token is the user's long-lived session credential. Memory-only persistence reduces its exposure - the token is never written to disk, is gone on reload, and cannot be read at rest from localStorage - but it does not eliminate it: while the client is connected the token still lives in the host page's memory, so a script running on your page (an XSS) could still read it during a live session.

The long-lived-token-in-the-host-page problem is not solved by storage choice. It is solved by v2's token lifecycle: short-lived access tokens plus an httpOnly refresh cookie on connect.usemycontext.ai, so the host page never holds a long-lived credential at all. Until then, treat the v1 token as sensitive and keep your page free of untrusted scripts.

If you want the token to survive reloads, opt in knowingly (this trades the above reduction away - the token is now also readable at rest from localStorage):

import { useMyContext, webStorage } from "usemycontext";

// You accept that the token now lives in localStorage, readable by any
// script on the page. The httpOnly-cookie model (no token in the host page)
// lands in v2.
useMyContext({ storage: webStorage(localStorage) });

Pass storage: null to use a no-op store, or supply your own StorageAdapter:

useMyContext({ storage: null });

License

MIT