rockin-worker
Provides tools to sync and query GitHub repositories, including getting top repositories by stars and searching by name.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@rockin-workerShow me the top 5 GitHub repos by stars."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Rockin Worker
Created and maintained by Bryant Giorgini.
Give an LLM agent a trustworthy, always-available view of your data — without letting it near your APIs.
Rockin Worker syncs data from third-party APIs (GitHub, Notion) into a local SQLite store, then exposes that store to Claude two ways: as a Model Context Protocol (MCP) server for Claude Desktop and any MCP host, and through a built-in tool-use agent (ask). A small eval harness measures whether the agent picks the right tool, answers from real data, and resists prompt injection.
flowchart LR
subgraph Upstream
GH[GitHub API]
NO[Notion API]
end
subgraph Worker
direction TB
E[Sync engine<br/>pagination · watermark · transactions]
DB[(SQLite<br/>WAL · versioned migrations)]
R[Tool registry<br/>single source of truth]
end
GH & NO -->|adapters: fetch + validate| E --> DB
DB --> R
R --> M[MCP server<br/>stdio · HTTP]
R --> A[Agent loop<br/>Anthropic Messages API]
R --> EV[Evals]
M --> CD[Claude Desktop / MCP hosts]Why this shape. Tools only ever read a local snapshot. The agent stays fast, keeps working when an upstream API is down or rate-limited, cannot be talked into calling an API it shouldn't, and every answer carries an honest lastSyncAt freshness stamp.
Quickstart
Prerequisites: Node.js ≥ 24 (.nvmrc provided; uses the built-in node:sqlite, so there are no native modules to compile) and at least one data-source credential:
GitHub — a personal access token with read access to your repositories.
Notion — an internal integration token; share the pages you want synced with that integration.
Anthropic (only for
ask/eval) — an API key from the Console.
npm install
cp .env.example .env # then fill in your credentials
npm run sync # sync every configured source (incremental after the first run)
npm run tool -- github_getTopItems --limit 5 # call a tool locally — no LLM involved
npm run ask -- "Which of my repos has the most stars, and which Notion page did I edit last?"
npm run serve # MCP server on stdioTool arguments are passed as flags (--limit 5, --query roadmap) so they work the same in bash, PowerShell and cmd. A JSON object is also accepted where your shell allows it: npm run tool -- github_getTopItems '{"limit":5}'.
Related MCP server: GitHub MCP Server
Configuration
All configuration is environment variables, read from the process environment first and from .env second. Blank values count as unset.
Variable | Default | Purpose |
| — | Enables the GitHub source. |
| — | Enables the Notion source. |
|
| Also sync page bodies (several extra Notion API calls per changed page, paced to ~3 requests/s). |
| — | Required for |
| — | Enables semantic search (embeddings come from Voyage AI; Anthropic has no embeddings API). |
|
| Embedding model. Changing it re-embeds everything on the next |
|
| Cosine-similarity floor for semantic matches. Scales differ per model — calibrate it. |
| — | Shared bearer token for |
| — | Switch |
|
| Scope an access token must carry. |
| — | Key-set URL, when the authorization server does not publish standard metadata. |
|
| Requests per client per minute on the HTTP endpoint ( |
|
| Interface |
|
| Port for |
| — | Comma-separated browser origins allowed to call the HTTP server. Non-browser clients send no |
|
| Model used by |
|
| SQLite file. Use |
|
| Where |
|
| Page budget per source per run. If it runs out before the data does, progress is saved and the next run resumes from the saved cursor. |
Commands
Command | What it does |
| Sync all configured sources, or one. Append |
| Build or refresh the semantic-search index (needs |
| Run one tool locally and print its JSON. Without a name it lists the available tools. |
| Ask the Claude agent (tools run in-process). Tools used, tokens and latency go to stderr. |
| MCP server over stdio. Add |
| Manage the OAuth credentials the HTTP server refuses. Takes effect on the next request. |
| End-to-end agent evals against fixture data. Needs |
| Everything CI runs: lint, typecheck, tests with a coverage floor. |
| Compile to |
Exit codes: 0 success · 1 runtime failure · 2 usage error.
Use it from Claude Desktop
npm run build// claude_desktop_config.json — use absolute paths (on Windows, escape backslashes: "C:\\Users\\you\\rockin-worker\\dist\\main.js")
{
"mcpServers": {
"rockin-worker": {
"command": "node",
"args": ["/absolute/path/to/rockin-worker/dist/main.js", "serve"],
"env": { "DB_PATH": "/absolute/path/to/rockin-worker/data.db" }
}
}
}Run npm run sync first — the server only serves what has been synced.
Remote MCP over HTTP
For clients that are not on the same machine, the same tools are available over streamable HTTP. There are two ways to authenticate; pick one.
Shared token — simplest, right for one person or one trusted team:
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)" # required, at least 24 characters
npm run serve -- --http --port 8787 # binds to 127.0.0.1 by defaultcurl -s http://127.0.0.1:8787/mcp \
-H "Authorization: Bearer $MCP_AUTH_TOKEN" \
-H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'The server fails closed (it refuses to start without a token), never accepts the token as a command-line argument (it would appear in the process list and shell history), compares it in constant time before reading the request body, and validates Host and Origin as the MCP specification requires against DNS-rebinding. It is stateless (no sessions), POST-only, and caps request bodies. It speaks plain HTTP: for anything other than loopback, put a TLS-terminating reverse proxy in front — a bearer token over cleartext is readable on the network, and the server warns when bound to a non-loopback address. GET /healthz is an unauthenticated liveness probe.
OAuth 2.1 — when several people or clients connect, and you need to know which is which, cut one off, or stop one from hogging the server. The server is an OAuth resource server: it validates access tokens issued by an authorization server you already run or rent (Keycloak, Auth0, Entra ID, …) and issues nothing itself.
export MCP_OAUTH_ISSUER=https://auth.example.com # who signs the tokens
export MCP_OAUTH_AUDIENCE=https://mcp.example.com/mcp # this endpoint, as clients see it
npm run serve -- --httpAn unauthenticated request gets 401 with WWW-Authenticate: Bearer resource_metadata="…"; that URL serves the RFC 9728 protected-resource metadata naming the authorization server, which is how an MCP client (Claude, for one) discovers where to log in. A token is accepted only if its signature verifies against the server's published keys (asymmetric algorithms only — never none or HS*), it has not expired, iss and aud match (RFC 8707: a token minted for another API is useless here), it carries the required scope, and it identifies a client or user. Failures are answered as RFC 6750 prescribes: 401 invalid_token, 403 insufficient_scope (naming the scope to request), and 503 with Retry-After when the authorization server is unreachable, because that is not the client's fault.
Identity per client. Rate limits and revocations apply to the token's
client_id(orazp, orsub).Revocation. A signed token stays valid until it expires wherever its signature checks out, so this server keeps a deny-list in the database:
npm run auth -- revoke --client <id>(or--subject, or--token <jti>) refuses it from the next request of a running server,auth unrevokelifts it,auth listshows it. Prefer short-lived tokens all the same.Rate limiting. A sliding window per client (
MCP_RATE_LIMIT_PER_MINUTE, default 120), answered with429andRetry-After, and advertised on every response inRateLimit-Limit/RateLimit-Remaining/RateLimit-Reset. It applies to the shared token too, as one identity.
Docker
docker build -t rockin-worker .
docker run --rm --env-file .env -v rockin-data:/data rockin-worker sync all
docker run -i --rm -v rockin-data:/data rockin-worker serve # -i keeps stdin open for stdio
docker run -d --restart unless-stopped --env-file .env -v rockin-data:/data rockin-worker sync all --every 15m # workerThe image runs as a non-root user and keeps its database and logs in the /data volume.
How it works
Sync engine (src/core/sync/engine.ts) owns the loop; a source only supplies an adapter (fetchPage, parse, upsert — see src/sources/github/adapter.ts). Guarantees:
Incremental. A per-source watermark lets a run stop paginating as soon as it reaches already-synced records.
At-least-once, idempotent. Records equal to the watermark are revisited on purpose (Notion timestamps have minute granularity, so a same-minute edit would otherwise be lost). Upserts are atomic
INSERT … ON CONFLICT DO UPDATE.Resumable backfill. If the page budget runs out, the pass is saved (cursor, start time, newest record seen) and the next run continues from it — even across a worker restart. The watermark only moves when the whole pass completes; advancing it earlier would skip the older, unseen records forever. A pass only resumes in the mode it started in (full vs incremental).
Transactional pages. Each page commits or rolls back as a unit; the summary counts only committed rows.
Validated input. Every upstream record is parsed with Zod. One malformed record is skipped and counted; an unexpected response envelope aborts the run.
Resilient HTTP. Per-attempt timeouts; retries on 429/5xx/network errors with backoff + jitter, honoring
Retry-After(seconds or HTTP-date) and GitHub's primary rate limit (403+X-RateLimit-Reset). A server-directed wait longer than 60 s makes the run fail fast instead of hanging.Deletions. A completed
--fullpass prunes records it never touched (every upsert stampssyncedAt, so "not touched since the pass began" means "deleted upstream"), even when the pass spanned several runs. It never prunes after an error or an unfinished pass, nor when the pass saw no records at all (that points to a token-scope or outage problem, not an empty account).Worker mode.
--everyruns non-overlapping cycles with jitter; a failing cycle is logged and the worker carries on. SIGINT/SIGTERM let the cycle in flight finish, close the MCP transport and the database, then exit.Observable. Every run appends one structured line to
logs/sync-history.jsonl.
Search is ranked, not substring matching: SQLite FTS5 with stemming ("habits" finds "habit tracker") and BM25 weighting (a name match outranks a description match) over each repository's name, description and topics and each Notion page's title and body (a title match outranks a body match, and a body match returns a snippet with the matched words marked «like this»), with a substring fallback for text inside a word. Queries are turned into quoted prefix terms, so model-supplied text can never inject FTS syntax.
Semantic search is optional and additive. With VOYAGE_API_KEY set and embed run, each search also ranks the local vectors by cosine similarity and fuses both rankings with Reciprocal Rank Fusion (which needs no score calibration between BM25 and cosine), so "routine" finds a repository described as "track daily habits" although they share no word. It is never a dependency: with no key, no index, or a provider outage, search silently degrades to the lexical ranking and says so in the result's note. Only the query text leaves your machine (to the embeddings provider); the synced data is embedded by embed, never by a tool call.
Tools are defined once (src/tools/define.ts, one file per source in src/tools/) and consumed by the MCP server, the agent and the evals, so the name, description and schema the model sees cannot drift between them. Model-produced arguments are validated before execution, and MCP tools are annotated readOnlyHint so hosts can safely auto-approve them. Each tool also declares an output schema derived from the same Zod types as its query, and the MCP server returns typed structuredContent (plus the text fallback); a contract test parses every tool's real output against its schema.
Agent (src/agent/agent.ts) is a minimal tool-use loop on the Anthropic Messages API with a turn cap, output truncation, and tool errors returned to the model rather than thrown. The system prompt and tool definitions are marked as prompt-cache breakpoints (they are identical on every turn), and cache reads/writes are reported alongside token usage.
Evals (src/evals) run the real agent against deterministic fixtures in a throwaway in-memory database and score each case on routing (right tool called?) and grounding (does the answer contain what the tool returned?). Cases include an honest-empty-result check and two prompt-injection cases: a Notion page title, and a Notion page body returned in a search snippet, that try to hijack the agent.
Design rationale, the sync algorithm, invariants, failure modes and a guide to adding a source are in docs/ARCHITECTURE.md.
Observability
Sync runs, HTTP calls, tool calls and agent turns are instrumented with OpenTelemetry, as traces and as metrics. Nothing is exported unless you point it at a collector, using the standard variables:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 # any OTLP/HTTP collector: Jaeger, Tempo, Honeycomb, …
export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=…" # optional, e.g. a SaaS backend's key
export OTEL_SERVICE_NAME=rockin-worker # optional
export OTEL_METRIC_EXPORT_INTERVAL=60000 # optional, ms between metric exports (default 60 s)
npm run syncA sync produces a sync <source> span (the run summary — fetched, inserted, deleted, errors, capped, resumed — as attributes) with one sync.fetch_page child per page and the HTTP client span under it. The agent produces invoke_agent → chat <model> (per model call) and execute_tool <name>, following the OpenTelemetry GenAI semantic conventions: model, finish reason, and input/output/cache token usage.
Metrics (exported to /v1/metrics on the same endpoint; OTEL_METRICS_EXPORTER=none or OTEL_TRACES_EXPORTER=none switches one signal off):
Metric | Type | Attributes |
| histogram (s) |
|
| counter |
|
| histogram (s) | method, |
| histogram (s) |
|
| histogram (s) | model, |
| histogram | model, |
Attributes are always low-cardinality (fixed sets of sources, tools, methods, hosts and outcomes — never a URL, an id or free text), so user input cannot flood a metrics backend. A run that finishes before the next export interval still reports: shutdown performs a final collection.
Privacy by design. Prompts, answers and tool output are never recorded (the GenAI conventions make content opt-in), URLs are recorded without their query string, and credentials never appear — a test asserts all three. Traces are best-effort: an unreachable collector cannot hang or fail the program, and OTEL_SDK_DISABLED=true turns everything off.
Project layout
src/
config/ validated environment
infra/ db (WAL, migrations) · http (retries) · logging · telemetry (OpenTelemetry) · embeddings
core/ sync engine + SyncAdapter port, sync state, query helpers, hybrid search + indexer
sources/ github/ · notion/ (adapter, store, queries)
tools/ tool framework, per-source tool definitions, registry
mcp/ agent/ evals/ cli/ main.tsPackages depend only downward; a test (architecture.test.ts) enforces the layering, rejects import cycles and keeps sources isolated from each other.
Security model
Prompt injection. Synced text (repository names, page titles and bodies) is attacker-influenceable and reaches the model. Mitigations: tools are read-only, the system prompt frames tool output as data rather than instructions, and an eval regression-tests it. This reduces the risk; it does not eliminate it.
Credential containment. Pagination links come from the server. The GitHub adapter refuses to follow one to a different origin, so the bearer token cannot be exfiltrated.
Least privilege. Read-only credentials; tools cannot write and never call upstream APIs.
Hermetic tests. Tests run against an in-memory database and never read
.env, so they cannot touch your data or use your credentials.stdout is the MCP protocol channel. Diagnostics go to stderr, and
dotenvruns in quiet mode.
To report a vulnerability, see SECURITY.md.
Testing and quality
npm run check # lint + typecheck + tests + coverage floor — the same as CI
npm test # tests only
npm run test:coverage # tests with a coverage reportTypeScript
strictwithnoUncheckedIndexedAccess, ESM, Biome for lint and formatting.Test areas: the sync engine (pagination, watermark, rollback, capping), both source adapters against mocked HTTP, the retry client (fake timers), migrations against a hand-built legacy schema, the agent loop against a scripted model, the CLI, and a real MCP integration test that spawns the server over stdio and drives it with the official client.
CI (
.github/workflows/ci.yml): lint → typecheck → build → tests with coverage → Docker build and smoke test. Dependabot covers npm, Actions and Docker.Evals are not part of
npm testor CI: they call a paid API and are nondeterministic. Runnpm run evalbefore demos and after changing tool descriptions or the system prompt.
Known limitations
Deletions need a full sync. Incremental syncs cannot see deletions; run
sync -- --full(for example nightly) to prune them.Star counts can lag. GitHub's
updated_atmay not change when a repository is starred, so an incremental sync can miss pure star changes. Runsync -- --fullperiodically.Notion search only returns pages shared with the integration, and its index is eventually consistent. Page bodies are read as plain text (headings, lists, to-dos, quotes, callouts, code, table rows; up to 3 levels of nesting, 400 blocks and 20,000 characters per page); images, files and embedded databases are not. A body is fetched when a page is (re-)synced, so after upgrading run
sync:notion -- --fullonce to backfill existing pages.Rate limits are per process and in memory. Fine for one node; several instances each enforce the limit on their own. Requests that fail authentication are not counted (they have no identity) — limit those at the reverse proxy, which you need for TLS anyway.
OAuth: JWT access tokens only. Opaque tokens (RFC 7662 introspection) are not supported, and the key set is fetched over the network on first use, so a restart while the authorization server is down answers
503until it is back.SQLite is single-writer. Fine here (WAL lets the MCP server read during a sync); not a multi-tenant design.
Roadmap
Token introspection (RFC 7662) for opaque tokens · a shared rate-limit store for multi-instance deployments · eval score history and regression tracking.
Contributing
See CONTRIBUTING.md. Release notes are in CHANGELOG.md.
Author and license
Created and maintained by Bryant Giorgini.
© 2026 Bryant Giorgini. Released under the MIT License: you may use, modify and distribute this software, provided the copyright notice and license text are preserved in all copies and derivative works. Every source file carries an SPDX license identifier and copyright line, and node dist/main.js --version prints the author and license. To cite this project, use GitHub's "Cite this repository" button (CITATION.cff).
This server cannot be deployed
Maintenance
Related MCP Connectors
An MCP server that gives your AI access to the source code and docs of all public github repos
Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.
Capability registry for the agentic economy. Semantic search over verified MCP server listings.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceProvides MCP servers for GitHub API operations and SQL database queries, enabling users to interact with GitHub repositories and databases through natural language.3MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that provides tools for interacting with the GitHub API, enabling AI assistants to query repositories, pull requests, issues, commits, users, and more.385 npmISC
- AlicenseAqualityBmaintenanceMCP server that enables AI assistants to look up and analyze GitHub repositories, including stars, forks, description, open issues, and README content.265 npmMIT
- AlicenseBqualityCmaintenanceA comprehensive MCP server that exposes 73 GitHub API tools for managing repositories, pull requests, issues, actions, releases, search, and more, enabling natural language control of GitHub.73MIT