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

encoach

v0.7.4

Published

EnglishCoach MCP server — SQLite-backed vocab/grammar/known store with stdio + HTTP transports and a review UI

Downloads

458

Readme

encoach-mcp

EnglishCoach MCP server. SQLite-backed store for vocab / grammar corrections / known words, exposed to MCP clients (Claude Code, Codex, …) as tools, resources and a prompt, plus a small web review UI for marking words "known".

SQLite is the single source of truth ($ENCOACH_DIR/encoach.db, default ~/.encoach). On first run it imports any legacy english_level.md / english_log.md / known_words.md from $ENCOACH_DIR, backing the originals up to $ENCOACH_DIR/backups/.

Run modes

node server.js            # stdio MCP — one per client session (local clients)
node server.js serve      # network MCP over Streamable HTTP at /mcp + UI (k8s)
node server.js http       # review UI only (localhost:4487)
node server.js inject      # print profile + known words (SessionStart hook)

serve is stateless: a fresh MCP server+transport per POST /mcp, so it scales horizontally behind a Service. Routes: POST /mcp, GET /healthz, GET / (review UI), GET /word?term= (word detail page), GET /api/state, GET /api/word?term=, POST /api/mark-known, POST /api/unmark-known.

Env: ENCOACH_DIR (data dir), ENCOACH_DB (db path override), ENCOACH_DICT (ECDICT path, default $ENCOACH_DIR/stardict.db), PORT, HOST.

Client adapters (encoachctl)

The coaching behavior is one platform-neutral policy (policy/englishcoach-policy.md); each client gets a generated adapter so wording never drifts between Claude and Codex. bin/encoachctl renders that policy into per-client managed blocks — delimited by <!-- ENCOACH:BEGIN managed --> / END markers — and rewrites only what's between the markers, so hand-written notes in the same file survive.

encoachctl status                       # enabled / token / remote reachable / adapter drift
encoachctl update [--claude] [--codex]  # dry-run: show what would change
encoachctl update --write               # apply (timestamped backups in $ENCOACH_DIR/backups)
encoachctl doctor                       # verify /healthz, /inject, MCP initialize+tools/list, drift, hooks

Targets:

  • Claude~/.claude/output-styles/EnglishCoach.md; SessionStart and UserPromptSubmit hook scripts under ~/.claude/hooks/ (registered in settings.json); and the generated skills ~/.claude/skills/{english-log,english-eval}/SKILL.md.
  • Codex~/.codex/AGENTS.md plus the SessionStart / UserPromptSubmit hook scripts under ~/.codex/hooks/ (registered in hooks.json).

Skills are generated from templates/skills/ so they stay in sync with the policy instead of drifting as hand-authored files — english-log logs via the encoach_log MCP tool, and english-eval runs the heavy re-assessment in an isolated subagent through encoach_eval_data / encoach_set_profile. Neither reads legacy local markdown. (Skills are a Claude Code mechanism; Codex steers via AGENTS.md.)

The SessionStart hook fails loudly: if the token is missing or /inject is unreachable, it prints a visible warning instead of a silent no-op, so a degraded coaching layer is surfaced rather than swallowed.

Dry-run is the default — nothing touches your home config until --write. Drift is detected by comparing actual block content (not just a stored hash), so in-block hand-edits are caught and repaired by update --write.

Path overrides (for testing against a sandbox instead of real home config): ENCOACH_DIR, ENCOACH_CLAUDE_OUTPUT_STYLE, ENCOACH_CLAUDE_SETTINGS, ENCOACH_CLAUDE_HOOKS, ENCOACH_CLAUDE_SKILLS, ENCOACH_CODEX_AGENTS, ENCOACH_CODEX_HOOKS, ENCOACH_URL.

Multi-user / auth (Keycloak OIDC)

Auth is off by default — without OIDC_ISSUER the server is single-user and every request is the owner (user_id=1). Set OIDC_ISSUER to turn it on:

| env | meaning | |-----|---------| | OIDC_ISSUER | Keycloak realm issuer, e.g. https://keycloak.example.com/realms/master (enables auth) | | OIDC_CLIENT_ID | OAuth client id (default encoach) | | OIDC_CLIENT_SECRET | confidential client secret (k8s secret encoach-oidc/client-secret) | | BASE_URL | public base, for redirect URI + discovery (default https://encoach.example.com) | | ENCOACH_AUDIENCE | expected aud on access tokens (default = client id) | | SESSION_SECRET | optional; auto-generated and persisted in the DB if unset |

Data is partitioned per user (vocab/grammar/known/profile); the ECDICT dictionary stays shared. The first Keycloak login claims the owner row, so all pre-auth data becomes that user's.

Browser uses Authorization Code + PKCE → signed session cookie. MCP clients and the inject hook present a bearer: either a Keycloak JWT (validated against JWKS) or a personal access token minted at /account. Mark-known stays a human UI action. GET /.well-known/oauth-protected-resource advertises Keycloak for OAuth-capable clients (RFC 9728).

Word detail + dictionary

Clicking a candidate term in the review UI opens a dedicated page (/word?term=) that combines two sources:

  • agent-supplied context — the zh gloss + example sentence logged via encoach_log during a conversation;
  • extended definition — looked up on demand from ECDICT, a local read-only English→Chinese SQLite dictionary (stardict table: translation, phonetic, definition, POS, exam tags, word forms). Cached into vocab.detail on first lookup.

ECDICT is not in the image or git (206MB). Load it onto the PVC once with scripts/load-ecdict.sh. If absent, the word page degrades gracefully (shows context + external dictionary links only).

MCP surface

  • tools: encoach_log({kind:"grammar"|"vocab", ...}), encoach_eval_data(), encoach_set_profile({text})
  • resources: encoach://profile, encoach://known, encoach://log
  • prompt: encoach (behavior kernel)

Marking a word "known" is a human action in the review UI, not a model tool.

Local client wiring

# Claude Code (stdio)
claude mcp add encoach -s user -- node /path/to/encoach/server.js
# Codex ~/.codex/config.toml
[mcp_servers.encoach]
command = "node"
args = ["/path/to/encoach/server.js"]

SessionStart hook (both clients): sh /path/to/encoach/inject.sh.

For a remote (k8s) deployment, point HTTP-capable clients at http://<host>/mcp instead.

Container

docker build -t <registry>/encoach-mcp:0.1.0 .
docker push <registry>/encoach-mcp:0.1.0

Kubernetes

Manifests in k8s/ (namespace, PVC, Deployment, Service, Ingress) wired with kustomize. Single replica (SQLite single-writer on an RWO PVC), Recreate strategy, /healthz probes, non-root.

kubectl apply -k k8s
kubectl -n encoach rollout status deploy/encoach-mcp

Image: registry.example.com/encoach/encoach-mcp. Ingress: encoach.example.com (nginx + letsencrypt-prod TLS).

CI/CD (Gitea Actions)

.gitea/workflows/release.yaml triggers on tag v*: build amd64 image → push to Harbor tagged with the git tag → kubectl set image + rollout on the cluster.

git tag v0.2.0 && git push origin v0.2.0

Requires a registered runner and three repo Actions secrets:

| secret | value | |--------|-------| | HARBOR_USER | Harbor robot name, e.g. robot$encoach+pusher | | HARBOR_TOKEN | Harbor robot token | | KUBE_CONFIG | base64 of a kubeconfig scoped to patch deploy/encoach-mcp in ns encoach |

Runner: k8s/act-runner.yamlact_runner + privileged DinD sidecar. Create the registration-token secret first (see the file header), then kubectl apply -f k8s/act-runner.yaml.