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

mcp-jobber

v0.1.0

Published

Secure, multi-tenant Model Context Protocol server for the Jobber field-service platform.

Readme

Jobber MCP Server

A secure, multi-tenant Model Context Protocol (MCP) server for the Jobber field-service platform. It lets AI assistants read and act on a Jobber account, clients, jobs, quotes, invoices, and scheduling, through a small set of tools designed for safe, correct use. It runs over stdio for local use and over an authenticated, multi-tenant Streamable HTTP transport for hosted use.

Unofficial and community-built. This project is not affiliated with or endorsed by Jobber. Jobber's App Marketplace does not list MCP integrations, so this is a self-hosted reference implementation: you run it against your own Jobber developer app and account.

Why this one is different

Most public MCP servers ship with no authentication, unvalidated inputs, and no guardrails on destructive actions. This one is built the opposite way, security first:

  • OAuth 2.0 with a correct token lifecycle (refresh, expiry, rotation), no long-lived hardcoded keys.
  • Every tool input and every Jobber API response validated with zod.
  • Least-privilege OAuth scopes, documented and justified.
  • Financial and data-changing tools guarded by a preview-by-default (dryRun) plus explicit confirm.
  • No secrets, tokens, or stack traces in tool output.
  • Runs on stdio (local) and an authenticated Streamable HTTP transport (hosted), acting as an OAuth 2.1 resource server.
  • True multi-tenancy: each request is bound to its own tenant's Jobber credentials, proven by an automated cross-tenant isolation test.

Tools

Read tools:

| Tool | What it does | | --- | --- | | search_clients | Find clients by name, email, or phone. | | get_client_overview | One call: profile, open jobs, recent quotes, outstanding invoices. | | list_jobs | List jobs, filterable by status and search term. | | get_job | Full detail for one job, including line items and visits. | | find_overdue_invoices | Past-due invoices with client, balance, and days overdue, plus total outstanding. | | get_schedule | Visits in a date range, optionally by team member. |

Write tools (all preview by default, mutate only with dryRun: false and confirm: true):

| Tool | What it does | | --- | --- | | create_quote | Create a quote for a client with line items (auto-resolves the client's property). | | schedule_job | Create a scheduled visit on a job, optionally assigned to team members. | | send_invoice | Mark a draft invoice as sent. |

Security

This is the core of the project. Every item below is implemented in the code.

Authentication and token lifecycle

  • OAuth 2.0 authorization-code flow against Jobber. Client secret and tokens live only in .env and gitignored token files, never in source.
  • Access tokens (60-minute lifetime) are minted on demand from a refresh token and refreshed about 5 minutes before expiry.
  • Refreshes are single-flight: concurrent tool calls share one refresh, so a rotating refresh token is never redeemed twice at once.
  • The refresh token is persisted to a local, 0600-permission file and updated on every rotation, so the credential chain sustains itself across restarts.
  • The server loads .env and overrides inherited environment variables, so a stale value exported into the launching shell cannot silently shadow your configuration.

Multi-tenant design (hosted HTTP transport)

  • Each request's tenant comes only from its validated token, never from tool input.
  • Each tenant gets its own Jobber client and token-store file; one tenant's credentials are never used for another.
  • A session is bound to its tenant at creation and re-checked on every request, so a valid token for one tenant cannot drive another tenant's session. An automated test proves tenant B cannot reach tenant A's session.

Hosted transport authorization

  • The server is an OAuth 2.1 resource server. Every HTTP request must carry a bearer token, validated for signature, issuer, expiry, and audience (RFC 8707): a token not issued for this server is rejected.
  • Missing or invalid tokens receive a 401 with a WWW-Authenticate header pointing at the Protected Resource Metadata (RFC 9728), served at /.well-known/oauth-protected-resource.
  • The caller's token authenticates them to this server only and is never passed through to Jobber; Jobber credentials are a separate per-tenant secret the server holds as an OAuth client to Jobber.
  • The Origin header is validated (anti DNS-rebinding) and the server binds to localhost by default.

Input and output safety

  • Every tool input is validated with zod (types, ranges, enums, sensible defaults).
  • Every Jobber response is parsed with zod before use. Unexpected shapes fail safe with a clear message rather than propagating bad data.
  • Outputs are summarized. Internal cost/margin fields and unnecessary PII are deliberately not returned.

Least-privilege scopes

| Scope | Access | Why | | --- | --- | --- | | Clients | Read | Look up and summarize clients. | | Jobs | Read | List and detail jobs. | | Users | Read | Resolve team members for scheduling. | | Quotes | Read + Write | Read summaries; create quotes. | | Invoices | Read + Write | Find overdue invoices; mark as sent. | | Scheduled Items | Read + Write | Read the schedule; create visits. |

Deliberately not requested: Jobber Payments, Requests, Expenses, Tax Rates, Timesheets, Vehicles, Custom Field Configurations, Marketing. Notably, invoices are marked as sent without requesting payment-data access.

Injection and error safety

  • All GraphQL calls use parameterized variables. User input is never concatenated into a query string.
  • Errors returned to the client are actionable but safe: no stack traces, no secret values, no internal paths. Diagnostic detail is logged to stderr only, never to stdout (which carries the JSON-RPC protocol).

Observability and rate limits

  • Every Jobber API call is audit-logged to stderr (operation name, outcome, duration). No arguments, results, tokens, or PII are logged.
  • Both of Jobber's rate limiters (HTTP 429 and the cost-based query throttle) are detected and returned to the caller as a clear, retryable message, while every other failure stays a safe generic message with no internal details.

Destructive and financial guards

  • Every write tool previews by default (dryRun: true) and performs no mutation unless called with dryRun: false and confirm: true.
  • Mutations surface Jobber's userErrors instead of reporting a false success.

Requirements

  • Node.js 22 or newer (the server uses native .env parsing and fetch).
  • A Jobber developer account, an app registered in the Jobber Developer Center, and a developer test account.

Setup

  1. Install dependencies and build:
    npm install
    npm run build
  2. Register an app in the Jobber Developer Center. Set the redirect URI to http://localhost:3000/oauth/callback and select the scopes in the table above. Scopes cannot be changed after creation.
  3. Copy the env template and fill it in:
    cp .env.example .env
    Set JOBBER_CLIENT_ID, JOBBER_CLIENT_SECRET, and JOBBER_API_VERSION (for example 2025-04-16).
  4. Obtain an initial refresh token. Open the authorize URL in a browser (with your client id):
    https://api.getjobber.com/api/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:3000/oauth/callback&state=setup
    Approve access, copy the code from the redirect URL, and exchange it:
    curl -X POST https://api.getjobber.com/api/oauth/token \
      -d grant_type=authorization_code \
      -d client_id=YOUR_CLIENT_ID \
      -d client_secret=YOUR_CLIENT_SECRET \
      -d redirect_uri=http://localhost:3000/oauth/callback \
      -d code=THE_CODE
    Put the returned refresh_token in .env as JOBBER_DEV_REFRESH_TOKEN (quoted).

The server mints and refreshes access tokens from there. If refresh-token rotation is enabled on your app, the server persists each new token to .jobber-token.json; keep the server as the only process that redeems the token.

Running the hosted HTTP transport

The HTTP transport adds authenticated, multi-tenant access and needs three more env vars:

MCP_ISSUER=https://dev-issuer.jobber-mcp.local   # token issuer this server trusts
MCP_RESOURCE_URL=http://127.0.0.1:3000/mcp       # this server's canonical URL (token audience)
MCP_DEV_JWT_SECRET="..."                         # HS256 secret for dev tokens; openssl rand -hex 32

Map each tenant to its Jobber refresh token in a gitignored .tenants.json:

{ "tenant-acme": { "refreshToken": "..." } }

The tenant-dev tenant falls back to JOBBER_DEV_REFRESH_TOKEN, so local development needs no tenants file.

Start the server and mint a dev token:

npm run build && npm run start:http
TOKEN=$(npx tsx scripts/mint-dev-token.ts tenant-dev)

It serves a single /mcp endpoint plus public Protected Resource Metadata at /.well-known/oauth-protected-resource. Point an MCP client at http://127.0.0.1:3000/mcp in Streamable HTTP mode, with that bearer token.

For production, swap the dev HS256 verifier in src/auth/token-verifier.ts to validate against a real OAuth 2.1 authorization server's JWKS. The wiring does not change.

Connecting to an MCP client

Over stdio, with the MCP Inspector:

npx @modelcontextprotocol/inspector node dist/index.js

With a desktop MCP client, add a server whose command is node with the argument pointing at dist/index.js, providing the environment variables from your .env. Run it from the project directory so the server can find .env and its token file.

Over HTTP: start the server (npm run start:http), then run the Inspector with no command (npx @modelcontextprotocol/inspector), choose Streamable HTTP, use http://127.0.0.1:3000/mcp, and set the bearer token.

Development

npm run dev        # stdio server with tsx (watch)
npm run start:http # hosted HTTP transport (built)
npm run dev:http   # HTTP transport with tsx watch
npm run mint-token # mint a dev bearer token (arg = tenant id)
npm run typecheck  # tsc --noEmit
npm run lint       # eslint
npm test           # vitest
npm run build      # compile to dist/

TypeScript is strict, the project is ESM (NodeNext), and tests live next to the code in __tests__/.

Architecture

  • src/index.ts picks the stdio transport and connects. src/server.ts is the transport-agnostic composition root that registers tools.
  • src/http.ts is the hosted entrypoint: createApp(deps) builds the Express app (Streamable HTTP transport, resource-server auth, per-tenant binding); main() wires the real dependencies.
  • src/jobber/ isolates all API access: the GraphQL client, the OAuth refresh, and the rotation-safe token store.
  • src/auth/ holds the token verifier; src/tenancy/ resolves per-tenant credentials.
  • src/tools/ holds one file per tool. src/config/ handles env loading and validation.

Limitations and roadmap

  • The hosted transport ships with a development (HS256) token issuer. A production deployment points at a real OAuth 2.1 authorization server by swapping the verifier to JWKS validation.
  • send_invoice sets an invoice's status to sent via Jobber's API. It does not itself email the client (Jobber exposes no send-email mutation).
  • Automatic retry with backoff on rate limits (beyond detecting and surfacing them) is a future enhancement.
  • CI audits production dependencies (npm audit --omit=dev). A DoS advisory exists in a deep dev-only transitive (brace-expansion, via ESLint); clearing it fully requires ESLint 10, which the current typescript-eslint does not yet support. It never ships in the package.

License

MIT