使用说明
MCP Servers
Personal MCP servers collection — Python 3.13 + FastMCP, managed with mise and uv.
Requirements
- mise (
brew install mise) — provisions Python 3.13 and uv automatically from.mise.toml. - That's it. mise picks up
uvfrom[tools]in.mise.toml; you don't need to installpythonoruvyourself.
Quick Start
git clone https://github.com/swistaczek/mcp.git
cd mcp
mise install # provisions Python 3.13 + uv from .mise.toml
mise run install # uv sync — installs all project deps from uv.lock
mise run test # 141 tests, ~3s, no network
To register the servers with Claude Code (user-scope, one-shot):
mise run claude-install # registers key-less servers
GEMINI_API_KEY=sk-... mise run claude-install-gemini # registers Gemini servers
Then restart Claude Code and the servers appear under their slug names (czynaczas, gazetki, tablica, exif_extractor, domains, krs, poznan_events, plus gemini_image_descriptions and plate_recognition if you ran the second task). Remove with mise run claude-uninstall.
Configuration
API keys
gemini_image_descriptions and plate_recognition need GEMINI_API_KEY (free at AI Studio). Two ways to provide it:
- Claude Code settings — add to
.claude/settings.local.json(gitignored):{ "env": { "GEMINI_API_KEY": "your-key-here" } } - Environment —
cp .env.example .env, edit, and usemise run claude-install-geminiwhich forwards the var into the registration viaclaude mcp add -e.
Two install paths
Servers can be registered two ways and both work:
- Direct python (recommended) — what
mise run claude-installdoes. Points Claude Code at.venv/bin/python <server>.py. Fast cold-start, no fastmcp.json indirection, nouvshell-out. - fastmcp.json — what
.mcp.jsonuses (read by Claude Code on session start when this repo's directory is its cwd). Goes throughuv run --project . fastmcp run <server>.fastmcp.json. RequiresuvonPATHand the project's cwd to be this repo.
If you bind the repo as a workspace in Claude Code, .mcp.json is the easy path. For user-scope (always-on) registration outside this repo, use mise run claude-install.
Available Servers
Czy Na Czas (czynaczas.py)
Polish public-transport realtime data via czynaczas.pl — vehicle positions, delays, ETAs.
- Cities:
poznan,warsaw,krakow,wroclaw,lodz - Tools:
list_supported_cities— list the five citiesfind_stops(city, query)— diacritic-insensitive multi-token search across the 3000+ stop list per city ("Dabrowskiego" matches "Dąbrowskiego")get_trip(city, trip_id)— route polyline + scheduled stopsget_vehicle(city, vehicle_id)— fleet metadata (model, depot, A/C, low-floor)get_departures(city, stop_id, destination_filter?, line_filter?)— headline tool. Snapshots all live vehicles via Socket.IO, filters by destination headsign + line, and computes ETA from each candidate's scheduled arrival plus realtime delay.
- Usage: "Kiedy najbliższy tramwaj 16 z Polna na Ogrody?" →
find_stops→get_departures(stop_id, destination_filter="Ogrody").
Gazetki (gazetki.py)
Current promotional flyer PDFs from Polish supermarket chains — Lidl and Biedronka.
- Tools:
list_flyers(chain)—lidl/biedronka/alldownload_flyer(chain, flyer_id)— streams native PDFs (Lidl) or assembles from per-page images (Biedronka)get_current_flyers(chain?)— convenience: headline current flyer per chain
- Cache:
~/.cache/mcp-gazetki/<chain>/<flyer_id>/(override withGAZETKI_CACHE_DIR).
Tablica Rejestracyjna PL (tablica.py)
Polish license-plate violation reporting via tablica-rejestracyjna.pl.
- Tools:
fetch_comments,submit_complaint(with image upload + HEIC→JPEG)
EXIF Metadata Extractor (exif_extractor.py)
Extract EXIF + GPS from images and reverse-geocode to street addresses.
- Tool:
analyze_image_metadata(up to 50 images, PNG/JPEG/HEIC, Nominatim geocoding)
Plate Recognition (plate_recognition.py)
Identify license plates and traffic violations in photos via Gemini Vision.
- Tool:
recognize_plates(multi-vehicle, pedestrian-perspective reasoning) - Setup: requires
GEMINI_API_KEY
Image Descriptions (gemini_image_descriptions.py)
Generate alt text and accessible descriptions for images and GIFs via Gemini.
- Tool:
generate_image_descriptions(batch up to 20 images, GIF support via FFmpeg) - Setup: requires
GEMINI_API_KEY
KRS Poland (krs.py)
Polish National Court Register (KRS) Open API — fetch the current or full extract for any registered entity.
- Tools:
get_krs_current_extract(krs, rejestr?)— odpis aktualny: name, NIP, REGON, formaPrawna, address, board, capitalget_krs_full_extract(krs, rejestr?)— odpis pełny: full historical record (past names, board changes, all entries)
- Inputs: KRS is 10 digits starting with
0(e.g.0001236495);rejestrisP(companies) orS(associations/foundations), defaults toP. A 204 response hints at trying the other register. - Source:
api-krs.ms.gov.pl/api/krs(no key required). Port of krs-poland-mcp-server.
Poznań Events (poznan_events.py)
City of Poznań events calendar (Co? Gdzie? Kiedy? — poznan.pl/mim/events/).
- Tools:
list_events(category?, page?, pages?)— paginated event list (20 per page); optional category id filter; up to 5 pages per callget_event(event_ref)— full detail (title, ISO date, time, place, categories, short + long description, hero image). Accepts numeric id, slug+id, path, or full URLlist_event_categories— catalog of category names with numeric ids (Sport=214, Muzyka=217, Sztuka=218, Teatr, Film, Książki, Dziecko, …)
Label Printer (label_printer.py)
Prints courier labels (InPost, DPD, DHL…) on a direct-thermal label printer attached to this machine. Works as an MCP server and a standalone CLI.
- Tools:
list_thermal_printers— detects connected label printers, whether each has a CUPS queue, and its page-description languageprint_label(file_path, printer?, label_size?, copies?, darkness?, speed?, threshold?, bold?, dry_run?)— renders and prints a PDF or image; creates the CUPS queue on first useprint_qr_code(data, printer?, module_dots?, error_correction?, caption?)— prints a QR code for any URL or textget_printer_status(printer?)— queue state and pending jobscancel_print_jobs(printer?, job_id?)— clear the queue
- CLI:
uv run python label_printer.py detect | status | install | render | print | qr | cancel(add--jsonfor machine-readable output) - Two transports: CUPS-backed label printers (Zebra EPL2/ZPL, Dymo) go through
lp; cheap ESC/POS receipt printers, which no bundled CUPS driver can drive, are handled directly over libusb byescpos_printer.py. Both are listed together and picked automatically. - Rendering: thermal heads are 1-bit, so the label is rasterised at 4× resolution, box-filtered down to the exact dot grid, and hard-thresholded to pure black/white. Letting the print system dither an anti-aliased page instead produces visibly faint output with barcodes that scan poorly.
- Auto-detection: with a single thermal printer attached,
printercan be omitted everywhere. Zebra, Dymo, TSC, Godex, Citizen, Bixolon, Sato, Argox, Intermec, Honeywell, Brother QL, Toshiba TEC and Seiko devices are recognised. - Requires:
pdftoppm(brew install poppler) for PDF input, plus the CUPS command-line tools. Verified on macOS with a Zebra TLP2844 (USB, EPL2, 203 dpi) and 4×6" stock. - Usage: "Print this InPost label" →
print_label(file_path="~/Downloads/label.pdf"). Darker output → raisethreshold(up to 254) or setbold=1.
Domain Checker (domains.py)
Batch domain registration check via WHOIS with DNS fallback and optional OVH browser-verified availability.
- Tool:
check_domains(up to 50 domains, 50+ TLDs incl. .com.cn and Chinese IDN)
Google Ads Planner (google_ads_planner.py)
Keyword research, demand forecasting and budget planning against the Google Ads API (v25, REST).
- Tools:
google_ads_keyword_metrics(keywords, geo_target_ids?, language_id?, network?, start_year_month?, end_year_month?)— "how much demand is there for these exact terms?" Historical average monthly searches, competition band + index, top-of-page bid range, and the month-by-month volume series. Up to 10,000 keywords per call.google_ads_keyword_ideas(seed_keywords? | seed_url? | seed_site?, geo_target_ids?, language_id?, network?, limit?)— "what else should we be targeting?" Expands one seed kind (max 20 seed keywords, or a single URL/site) into related keywords, each scored with anopportunity_score(volume ÷ competition), plus ready-madetop_by_volume,top_opportunitiesandeasy_targets(LOW/MEDIUM competition) buckets.google_ads_forecast_budget(keywords, match_type?, max_cpc_bid, daily_budget?, start_date?, end_date?, …)— "what would this cost and what would we get?" Forward-looking forecast for a hypothetical campaign: clicks, cost, average CPC, conversions, average CPA.google_ads_budget_curve(keywords, match_type?, bids?, …)— "where does spending more stop paying off?" Sweeps the forecast across a bid ladder (default0.25 … 8.00, max 10 points), computes marginal clicks / marginal cost / marginal cost-per-click between consecutive points, and picks thebest_efficiency_bid.google_ads_seasonality(keywords, months_back?, geo_target_ids?, language_id?)— "when in the year should we spend?" Up to 48 months of history normalised into a monthly index (1.0 = average month), with peak/trough months per keyword and in aggregate, plus year-over-year change.
- Defaults: geo
2840(USA), language1000(English), networkGOOGLE_SEARCH. UK is2826, Canada2124; Spanish1003, French1002. - Setup: see Google Ads Planner setup below — credentials come from 1Password, not from files.
- Caveats:
- A Basic-access-tier developer token is required. Test and Explorer tier tokens cannot call the keyword planning endpoints at all — they fail regardless of the account or the query. Apply for Basic access in your manager account's API Center.
- Keyword planning is rate-limited to 1 QPS per customer id. The server serialises and paces every request itself, so a
google_ads_budget_curveover six bids takes ~7s of wall clock by design. Don't fan these tools out in parallel. - The v25 forecast returns no impressions and no CTR — those fields were removed in v24. You get clicks, cost, average CPC, conversions and average CPA only; impression share and click-through rate cannot be derived from it.
- Absent numbers mean "no data", not zero. Bid and competition fields are frequently missing for long-tail keywords and come back as
null— don't read them as $0.00 or as zero competition.
- Source:
googleads.googleapis.comREST endpoints undercustomers/{id}:generateKeywordHistoricalMetrics/:generateKeywordIdeas/:generateKeywordForecastMetrics.
Google Ads Planner setup
The server reads four environment variables. Nothing secret is stored in this repo —
.mcp.json holds only 1Password references, and op run resolves them at launch.
| Variable | What it is |
|---|---|
GOOGLE_ADS_DEVELOPER_TOKEN |
API token from your manager account's API Center |
GOOGLE_ADS_SERVICE_ACCOUNT_KEY_B64 |
base64 of the GCP service-account JSON key |
GOOGLE_ADS_LOGIN_CUSTOMER_ID |
manager (MCC) account id, digits only |
GOOGLE_ADS_CUSTOMER_ID |
the client account actually queried, digits only |
GOOGLE_ADS_API_VERSION |
optional, defaults to v25 |
1. Get a developer token (Basic tier)
Only a manager (MCC) account can issue one. In the MCC: Tools & Settings → Setup → API Center (https://ads.google.com/aw/apicenter). Apply for Basic access — new tokens start on Explorer tier, which cannot call keyword planning at all, and Test tier only works against test accounts. Approval is manual, typically 1–3 business days.
2. Create the service account
Auth is a GCP service account with no impersonation (no domain-wide delegation, no Workspace admin needed). Create a JSON key, then add the service account's email as a user on the Ads account under Admin → Access and security. Encode the key:
base64 -i service-account.json | tr -d '\n'
3. Store the credentials in 1Password
Create one API Credential item — the reference implementation lives in the Startupkit
vault as Google Ads MCP:
| Field | Contents |
|---|---|
credential |
developer token |
ads_manager_account_id |
MCC id |
company_account_id |
client account id |
Service Account → SERVICE_ACCOUNT_KEY_B64 |
the base64 blob from step 2 |
Item titles must not contain
(,)or other punctuation —op://secret references reject them withinvalid character in secret reference. Stick to letters, digits, spaces and hyphens.
Verify each reference resolves before wiring it up:
op read "op://Startupkit/Google Ads MCP/credential"
4. Launch
.mcp.json already wraps the server in op run, so Claude Code picks it up with no
further setup as long as you have an active op session (op signin):
"args": ["-c", "op run --no-masking -- uv run --with fastmcp fastmcp run google_ads_planner.fastmcp.json"],
"env": {
"GOOGLE_ADS_DEVELOPER_TOKEN": "op://Startupkit/Google Ads MCP/credential"
}
--no-masking is required, not optional. op run otherwise scans stdout and
replaces anything resembling a secret with <concealed> — and for a stdio MCP server
stdout is the JSON-RPC channel, so masking can corrupt the protocol stream.
To point at a different vault or item, edit the op:// references in .mcp.json; they
are not secrets and are safe to commit.
Running outside Claude Code
Same mechanism, driven from a dotenv file of references:
cat > .env.google-ads <<'EOF'
GOOGLE_ADS_DEVELOPER_TOKEN=op://Startupkit/Google Ads MCP/credential
GOOGLE_ADS_SERVICE_ACCOUNT_KEY_B64=op://Startupkit/Google Ads MCP/Service Account/SERVICE_ACCOUNT_KEY_B64
GOOGLE_ADS_LOGIN_CUSTOMER_ID=op://Startupkit/Google Ads MCP/ads_manager_account_id
GOOGLE_ADS_CUSTOMER_ID=op://Startupkit/Google Ads MCP/company_account_id
EOF
op run --no-masking --env-file=.env.google-ads -- mise run dev google_ads_planner.fastmcp.json
.env* is gitignored. If you genuinely cannot use 1Password, plain values in .env
work too — but never put them in .mcp.json, which is committed.
Development
mise tasks # list all available tasks
mise run dev <server>.fastmcp.json # run a server in stdio mode
mise run test # quick (no integration)
mise run test-all # full (network + GEMINI_API_KEY needed)
To create a new server: copy czynaczas.py and czynaczas.fastmcp.json as a template, add the module name to [tool.setuptools] py-modules in pyproject.toml, and re-run mise run install. See FastMCP docs for the full pattern.