claude-tools
Provides tools for interacting with Google's Gemini AI, including asking questions, reviewing diffs, and analyzing files.
Click on "Install 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., "@claude-toolsAsk Gemini for a second opinion on my approach to refactoring the database layer."
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.
claude-tools
Local MCP servers that give Claude Code access to other tools mid-session.
gemini-bridge
Exposes three tools backed by the Gemini CLI:
ask_gemini(prompt, context="")— ask Gemini a question, e.g. for a second opinion on an approach.review_diff(repo_path, instructions="")— runsgit diffinrepo_pathand sends it to Gemini for critique. Pass the absolute path of the repo you want reviewed; the bridge runs as its own background process and does not share Claude Code's working directory.ask_gemini_about_files(file_paths, question)— reads one or more full files and asks Gemini a question about them. Use this instead ofask_gemini'scontextparam when the files are too large for Claude's own context, or when you want Gemini's take on whole files/modules rather than a truncated excerpt — Gemini's window is large enough to hold much more (up to 500k chars) thanask_gemini/review_diffallow (60k chars). Pass absolute paths; the bridge runs as its own process and does not share Claude Code's cwd.
Every call is logged to log.jsonl (gitignored) as an audit trail of what
was asked and answered.
Setup
Install the Gemini CLI:
npm install -g @google/gemini-cliAuthenticate with an API key — as of gemini-cli 0.50.0, the free
oauth-personallogin tier ("Gemini Code Assist for individuals") is no longer accepted; Google points individual users at a separate product (Antigravity) instead. Use an API key:Get a free key from Google AI Studio.
Put it in
~/.gemini/.env:GEMINI_API_KEY=your-key-hereSet
~/.gemini/settings.jsonto use it:{ "security": { "auth": { "selectedType": "gemini-api-key" } } }
Install this project's dependencies:
cd ~/git/claude-tools python3 -m venv .venv .venv/bin/pip install -r requirements.txtRegister the server with Claude Code (user scope, so it's available in every project):
claude mcp add gemini-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/gemini_bridge.pyRestart Claude Code / reload the window.
ask_gemini,review_diff, andask_gemini_about_filesshould show up as callable tools.
Notes
gemini_bridge.pyreads~/.gemini/.envitself and setsGEMINI_CLI_TRUST_WORKSPACE=trueon every call, rather than relying on gemini-cli's own env-file auto-discovery (unreliable when invoked as a subprocess from an arbitrary cwd) or its interactive trusted-folder prompt (which headless calls can't answer).Gemini CLI's
-p/--promptnon-interactive flag is flagged upstream as a candidate for future deprecation (gemini-cli#16025). If it's renamed, only_call_gemini()ingemini_bridge.pyneeds updating.Context and diffs are truncated to 60k characters before being sent to Gemini to avoid blowing past its context window.
ask_gemini_about_filesuses a much higher 500k-character ceiling, since its whole point is to use Gemini's larger window on full files.Before invoking the Gemini CLI,
_call_gemini()does a quick 5s TCP preflight check against Gemini's API host. On a flaky connection (e.g. a phone hotspot), this fails fast with a clear error instead of blocking for the fullGEMINI_TIMEOUT(120s). Restart Claude Code / reload the window after pulling this change, since the running server process won't pick it up otherwise.
Related MCP server: mcp-local-llm
ollama-bridge
Exposes three tools backed by a locally running Ollama instance:
prefilter_diff(repo_path, model="qwen2.5-coder:7b")— runsgit diffinrepo_pathand sends it to a local Ollama model for a cheap first-pass triage before spending areview_diff(Gemini) call on it. The response starts withCLEAN:orFLAGGED:— only escalate toreview_diffwhen it'sFLAGGED, or when this tool errors (e.g. Ollama isn't running); never skip review outright just because the local pass errored.triage_log(repo_path, log_path, model="qwen2.5-coder:7b")— reads a build/test log already written to disk (redirect a failing command's output first, e.g.cmd > out.log 2>&1) and sends it to a local Ollama model to pull out just the root failure (FILE:/ERROR:/CONTEXT:), instead of reading the whole raw log directly. ReturnsNO FAILURE FOUND.for a clean run.log_pathmust resolve insiderepo_path(absolute or relative, same containment rule asrepo-bridge'sget_file) — rejected otherwise.triage_transcript(repo_path, transcript_path, model="qwen2.5-coder:7b")— reads a transcript JSON file already written byyoutube-bridge'stranscribe_video(a list of{start, end, text}segments) and sends it to a local Ollama model to flag likely restarts, filler-heavy stretches, and dead air, instead of reading the whole transcript closely to spot them. Returns[MM:SS-MM:SS]timestamps with a one-line reason each, orCLEAN: ...if nothing stands out. Samerepo_path/transcript_pathcontainment rule astriage_log. Doesn't decide cuts — points at where to look before buildingkeep_segmentsforcut_video.
Every call is logged to ollama_log.jsonl (gitignored) as an audit trail.
Setup
Install Ollama and pull a coding-capable model:
ollama pull qwen2.5-coder:7bThis tool has no new dependency beyond what's already in
requirements.txt— it talks to Ollama's local REST API with the standard library'surllib, consistent with the rest of this repo's bridges.Register the server with Claude Code (user scope):
claude mcp add ollama-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/ollama_bridge.pyRestart Claude Code / reload the window.
prefilter_diffshould show up as a callable tool.
Notes
This exists to save Gemini API calls on routine commits, not to replace
review_diff. A quantized 7B local model catches the obvious stuff (dead code, naming, syntax slips) but reliably misses subtler bugs and security issues a frontier model catches — treat aCLEANverdict as "nothing obvious jumped out," not "safe to skip a real review," for anything consequential.Reuses
review_diff's own diff-selection order (vs HEAD, then staged--cached, then plain unstaged) so both tools always look at the same diff._call_ollamasets an explicitnum_ctx: 8192on every request. Ollama defaults to 2048 regardless of the model's real context length, which would silently left-truncate any diff nearMAX_CONTEXT_CHARSand dropPREFILTER_INSTRUCTIONSentirely — caught byreview_diffon this tool's own first commit before it ever shipped.temperature: 0.0is set alongside it for a more consistent CLEAN/FLAGGED verdict.Verified end-to-end against this repo's own pending
README.mddiff on 2026-07-24:prefilter_diffpicked up the real diff and returned aCLEANverdict fromqwen2.5-coder:7brunning locally — call and response both landed inollama_log.jsonlas expected.qwen2.5-coder:7bwas picked as the default because it's the only locally installed model (of qwen2.5-coder, dolphin-mistral, llama3, deepseek-coder, plus several custom personas) that reports tool-use/code capability rather than plain chat completion — override viamodelfor a different local model.triage_logreads an existing log file rather than executing a command itself — deliberately, to keep this bridge's subprocess surface limited togit diff(a fixed, safe command) rather than adding an arbitrary-command-execution tool. Redirect a failing command's output to a file first (cmd > out.log 2>&1), then triage that.triage_logtruncates from the start of the log via_truncate_keep_tail, the opposite ofprefilter_diff's_truncate— a build/test failure is almost always near the end of a log (the start is setup noise: dependency resolution, banners), unlike a diff where head-truncation is fine. Uses a largernum_ctx: 24576(LOG_NUM_CTX) thanprefilter_diff's8192sinceMAX_LOG_CHARS(40k chars) needs more headroom than a same-ratio scale-up would give —review_diffflagged that 40k chars could run denser than ~2 chars/token on symbol-heavy logs, soLOG_NUM_CTXwas set with margin rather than scaled proportionally toprefilter_diff's ratio.triage_logrequiresrepo_pathand resolveslog_pathinside it via the same_resolve_in_repocontainment checkrepo-bridgeuses forget_file— added afterreview_diffflagged the first draft (a barelog_pathwith no scoping) as an arbitrary-file-read risk: an unscoped path could be pointed at~/.ssh,.envfiles, etc._call_ollama'sURLErrorhandler checksisinstance(e.reason, TimeoutError)before the genericOSErrorcheck —review_diffcaught that a real (slow-but-reachable-Ollama) timeout is itself anOSErrorsubclass, so without checkingTimeoutErrorfirst it was misreported as "not reachable - isollama serverunning?" instead of a timeout.triage_logalways appends a pointer back to the fulllog_pathand line/char count alongside the model's summary — a 7B model can misidentify the root cause in a complex multi-error log, so the raw log stays oneReadaway rather than being fully replaced by the summary.Verified against a synthetic pytest log (42 tests, 1 real failure buried in passing-test noise): correctly extracted the failing file/line, exact
AssertionError, and relevant traceback lines, ignoring the noise. A second synthetic clean-run log correctly returnedNO FAILURE FOUND.triage_transcript(added 2026-08-06) reuses_resolve_in_repofromtriage_logand_truncatefromprefilter_diffrather than adding new helpers — a transcript is closer to a diff than a log for truncation purposes (an issue can be anywhere in it, not clustered near the end), so head-truncation is the right default, nottriage_log's tail-keeping one. Verified end-to-end against a synthetic 7-segment transcript containing one obvious mid-sentence restart and one filler-heavy stretch ("um, so, yeah, so basically, um") —qwen2.5-coder:7bflagged both correctly with their timestamps. Does not decidekeep_segmentsitself, by design —cut_video's docstring is explicit that cut decisions belong to the agent reading the actual transcript, not a local model summary.
tn3270-bridge
Exposes six tools backed by py3270 (which
drives s3270 under the hood) for automating TN3270 mainframe green-screen
sessions:
connect(host, port=23)— opens a session, returns asession_id.send_keys(session_id, keys)— typeskeysinto the current field. A"\n"inkeyspresses Enter (e.g."myuser\n"typesmyuserand submits it). Returns the resulting screen.read_screen(session_id, structured=False)— returns the current screen as plain text. Withstructured=True, returns JSON instead:{"cursor": {"row", "col"}, "fields": [{"row", "col", "protected", "hidden", "autoskip", "modified", "text"}, ...]}— use this when an agent needs to know where to type (which field is unprotected, which one is a hidden password field) rather than just what's on screen. Row/col are 1-indexed and refer to the field's attribute-byte position, so the first typeable character is one column to the right ofcol.read_panel_state(session_id)— returns a compact JSON summary instead of a full screen dump:{"messages": [{"id", "severity", "text"}, ...], "actionable_inputs": [{"label", "row", "col", "hidden"}, ...], "cursor": {"row", "col"}}.messagesis every line starting with a standard IBM message ID (ICH408I,IKJ56650I, etc.) — RACF/TSO/JES errors and status lines all follow that shape, so this generalizes across products instead of needing a per-panel lookup table.actionable_inputsis every unprotected field with a best-guess label pulled from the protected field immediately before it (e.g."Userid ===>"next to the input). Use this instead ofread_screenwhen the agent just needs to know what the screen says and where to type, not the full layout.send_function_key(session_id, key)— sendsPFn/PAn/Clear/Enter. Returns the resulting screen.disconnect(session_id)— closes the session.
Sessions live in memory for the lifetime of the server process (one per
Claude Code session) — disconnect any session you're done with rather than
letting it leak. Every call is logged to tn3270_log.jsonl (gitignored).
Setup
Install
s3270(part of the x3270 suite):brew install x3270Install this project's dependencies (shared
.venvwith gemini-bridge):cd ~/git/claude-tools .venv/bin/pip install -r requirements.txtRegister the server with Claude Code:
claude mcp add tn3270-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/tn3270_bridge.pyRestart Claude Code / reload the window.
Notes
Plain-text
read_screenuses x3270'sAscii()script command. Structured mode usesReadBuffer(Ascii), which annotates the buffer withSF(...)markers at each field's attribute byte; the byte's bits are decoded per the 3270 field-attribute spec (IBM GA23-0059) intoprotected/hidden/autoskip/modified.hiddenis what marks password fields — verified against a live TSO logon screen, wherePassword/New Password/MFA Tokenall came backhidden: trueandUseriddid not.Field
row/coland cursorrow/colcome from two different x3270 commands with different indexing (ReadBufferis 1-indexed,Query (Cursor)is 0-indexed) —_structured_screen()normalizes both to 1-indexed. Confirmed by checking the cursor lands one column past an unprotected field's attribute byte, which is where typing actually starts.The first five tools have been exercised against a live TN3270 host (a local test mainframe on
localhost:3270):connect+read_screenpulled back the logon banner,send_keys("TSO\n")advanced from the logon-type prompt to the TSO/E LOGON screen,send_function_key("PF3")logged off back to the banner, and structuredread_screencorrectly identified the one unprotected field on the banner screen and the hidden password fields on the TSO/E LOGON screen.read_panel_state(added 2026-08-06) has not been verified against a live host in this environment — no test mainframe was reachable, only unit tested against synthetic screen data (a fabricated RACF-style logon denial:ICH70001I/ICH408Imessage lines plusUserid/Passwordfields), which it parsed correctly. The message-ID regex is based on IBM's documented, standardized message format, and the label heuristic matches every real panel layout seen in this repo's earlier live testing, but both should be treated as unverified against a real host until run against one.actionable_inputslabels are a best guess, not authoritative — fall back to structuredread_screenif a label looks wrong.
repo-bridge
Exposes four tools for getting codebase context from a repo outside Claude Code's own working directory:
search_codebase(repo_path, query, max_results=50, ignore_case=False)— grepsrepo_pathforquery(a regex). Usesgit grepwhenrepo_pathis a git repo (respects.gitignore), otherwise plaingrep -r. Returnspath:line:contentper match.get_file(repo_path, path)— returns the full contents ofpath(relative torepo_path), truncated past 60k chars. Rejects paths that escaperepo_path(e.g.../../etc/passwd).list_structure(repo_path, max_entries=500)— returns a directory tree as indented text. Usesgit ls-files(tracked files only) whenrepo_pathis a git repo, otherwise walks the filesystem skipping common junk dirs (node_modules,.venv,__pycache__, etc.).get_symbol(repo_path, name, language="", max_results=10)— finds function/class/method/type definitions namednameand returns their source text via tree-sitter. Supportspython,javascript,typescript,tsx,go,rust,java,ruby,c, andcpp. Greps for files that referencenamefirst rather than parsing the whole repo, and returns every match found (up tomax_results), not just the first.
Pass the absolute path of the repo you want as repo_path for every tool —
this server runs as its own process and does not share Claude Code's
working directory.
Setup
Install this project's dependencies (shared
.venv):cd ~/git/claude-tools .venv/bin/pip install -r requirements.txtRegister the server with Claude Code:
claude mcp add repo-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/repo_bridge.pyRestart Claude Code / reload the window.
Notes
Uses the official per-language
tree-sitter-*packages (prebuilt wheels, compiled at install time), not thetree-sitter-language-packpackage — that one downloads grammars over the network on first use, which is a bad fit for this repo's other lesson-learned (see gemini-bridge's network preflight check above) and just failed outright when tried on a flaky connection.get_symbolmatches by each grammar'snamefield on definition-like node types (function_definition,class_declaration, etc.) — this works generically across languages without per-language field lookups._find_definitions()walks the whole tree (including inside already-matched nodes, so e.g. two same-named methods in two different classes in one file both surface) and results are capped atmax_results, defaulting to 10.C and C++
function_definitionnodes are the one exception to the genericname-field lookup above: their identifier is nested inside adeclaratorchain (apointer_declaratorfor pointer return types, etc.) ending in afunction_declaratorwhose owndeclaratorfield is the actual identifier._c_family_function_name()inrepo_bridge.pyunwraps that chain; struct/enum/union/class specifiers in C/C++ do expose anamefield directly and don't need it.Verified against this repo (Python) and small standalone fixtures for TypeScript, Go, Rust, Java, Ruby, C, and C++ —
get_symbolcorrectly pulled a function/struct (or class/method) from each, with correct line ranges.
linkedin-bridge
Exposes three tools for managing LinkedIn posts on behalf of the authenticated member, all via LinkedIn's Posts API:
post_to_linkedin(text, visibility="PUBLIC")— publishes a text post.visibilityis"PUBLIC"or"CONNECTIONS". Returns the live post URL on success.update_linkedin_post(post_url_or_urn, text)— replaces the text of an existing post. Takes either the live URL or a bare URN. Only needs thew_member_socialscope this app already has.delete_linkedin_post(post_url_or_urn)— permanently deletes a post. Irreversible.
All three publish, edit, or delete under the user's real identity — always confirm the exact action/text before calling any of them, never call them unprompted.
Every call is logged to linkedin_log.jsonl (gitignored) as an audit trail.
Setup
Create a LinkedIn Company Page, then a developer app at linkedin.com/developers/apps associated with it, with the Share on LinkedIn product added (grants the
w_member_socialscope). The app needs a real privacy policy URL — this repo's own consulting site's privacy page is an example of a minimal one.Add an Authorized redirect URL of
http://localhost:8765/callbackon the app's Auth tab, and note the Client ID and Client Secret.Put the credentials in
~/.linkedin/.env(create the file yourself in a text editor — don't paste secrets through an agent if avoidable):LINKEDIN_CLIENT_ID=... LINKEDIN_CLIENT_SECRET=...then
chmod 600 ~/.linkedin/.env.Run the one-time OAuth flow:
.venv/bin/python linkedin_oauth_setup.pyThis opens a browser for LinkedIn's consent screen, exchanges the resulting code for an access token, fetches the member's person URN, and writes both back into
~/.linkedin/.env.Register the server with Claude Code:
claude mcp add linkedin-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/linkedin_bridge.py
Notes
Standard LinkedIn apps don't get a refresh token without extra approval — access tokens last ~60 days. Re-run
linkedin_oauth_setup.pyonce one expires;post_to_linkedinwill surface LinkedIn's own error message if a call is attempted with an expired token.Uses
urllibfrom the standard library rather than adding an HTTP client dependency, consistent with the rest of this repo's bridges.Solved: the earlier "posts sometimes render truncated" issue was LinkedIn's "little" text format. The
commentaryfield isn't plain text — it's a small markup language for mentions/hashtags, and characters reserved for that markup (( ) [ ] { } @ # < > \ * _ ~) must be backslash-escaped to appear as literal text. Every post logged during the original investigation was checked against this: every single one containing an unescaped(or)rendered truncated in the feed, every one without either character rendered in full — 11/11 with no exceptions._escape_little_format()now escapes all reserved characters automatically before everypost_to_linkedin/update_linkedin_postcall, except#wordsequences (left alone so intentional hashtags still render as hashtags). Confirmed fixed with a live test post containing parentheses.update_linkedin_post/delete_linkedin_postdon't needr_member_social(LinkedIn's read-back permission, currently closed for new access requests) - only reading a post back to verify its content needs that, so there's still no way to check a post's live content without a human looking at it.Verified end-to-end: OAuth flow completed, a real post published successfully via
post_to_linkedin, then updated viaupdate_linkedin_post(tested with the full URL form) and deleted viadelete_linkedin_post(tested with the bare URN form) - both input styles work.The
Linkedin-Versionheader is pinned to the previous month (_linkedin_api_version()), not the current one. LinkedIn versions its REST API by month, but a brand-new monthly version isn't always live in production right at the start of that month, requests using the current month's version have come back with a 426 NONEXISTENT_VERSION error, while the previous month's version is always already rolled out.
youtube-bridge
Exposes five tools for a solo-creator video pipeline: transcribe a raw recording, mechanically tighten it, cut it down semantically, then upload it to YouTube on a schedule.
transcribe_video(video_path)— local faster-whisper transcription with timestamps. Writes<video_path>.transcript.jsonand returns[MM:SS - MM:SS] textlines for Claude to read and reason about what to cut.tighten_video(video_path, output_path="")— runs auto-editor for a mechanical first pass (cuts silence/dead air). Doesn't understand meaning — pair withcut_videofor semantic cuts (flubbed takes, restarts, rambling).cut_video(video_path, keep_segments, output_path="")— given a list of[start, end]second ranges to keep (picked by Claude from the transcript), re-encodes and concatenates via ffmpeg for frame-accurate cuts. Claude decides what to cut by reading the transcript; this tool only executes the mechanical trim.queue_video_for_upload(video_path, title, description, tags, category_id="28", publish_at="")— uploads asprivacyStatus: "private"withstatus.publishAtset, so YouTube itself flips the video public at that timestamp — no daemon or cron needed on this end. Ifpublish_atis omitted, computes the next open slot fromYOUTUBE_POST_TIMES(comma-separatedHH:MM, local time — one entry = 1/day, two = 2/day), reading/advancing state inyoutube_schedule_state.jsonso repeated calls in one batch-recording session spread out across days without double-booking. On success, moves the source file into aposted/subfolder (date-prefixed) so it drops out of the pending queue.list_pending_videos(folder="")— lists video files infolder(defaultYOUTUBE_VIDEO_DIR) not yet moved toposted/, i.e. what's left in a batch day's queue.
queue_video_for_upload schedules a video to go publicly live with no
further confirmation at publish_at — always confirm title/description/
tags/timing with the user before calling it, never call it unprompted, same
rule as linkedin-bridge.
Every call is logged to youtube_log.jsonl (gitignored).
Setup
Install
ffmpeg(brew install ffmpeg) and this project's Python dependencies (shared.venv,faster-whisperandauto-editorare inrequirements.txt):cd ~/git/claude-tools .venv/bin/pip install -r requirements.txtCreate a Google Cloud project, enable the YouTube Data API v3, and create an OAuth client of type Desktop app. Add
http://localhost:8766/callbackas an authorized redirect URI.Put the client credentials in
~/.youtube/.env:YOUTUBE_CLIENT_ID=... YOUTUBE_CLIENT_SECRET=... YOUTUBE_VIDEO_DIR=/path/to/your/raw-recordings-folder YOUTUBE_POST_TIMES=10:00,17:00then
chmod 600 ~/.youtube/.env.Run the one-time OAuth flow (forces
access_type=offline&prompt=consentso Google actually returns a refresh token):.venv/bin/python youtube_oauth_setup.pyThis writes
YOUTUBE_REFRESH_TOKENback into~/.youtube/.envand prints the authorized channel name to confirm you authorized the right account.Register the server with Claude Code:
claude mcp add youtube-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/youtube_bridge.pyRestart Claude Code / reload the window.
Notes
Unlike LinkedIn's ~60-day access token, Google's refresh token doesn't expire from normal use —
youtube_bridge.pyexchanges it for a fresh access token on every call rather than caching one, soyoutube_oauth_setup.pyshould only need to run once.publishAtrequiresprivacyStatus: "private"at upload time (YouTube rejects a scheduledpublic/unlistedupload) — the tool always sends"private", which is what triggers YouTube's own scheduling behavior.Uses
urllibfrom the standard library for OAuth and the resumable-upload protocol, consistent with the rest of this repo's bridges — nogoogle-api-python-clientdependency. The upload streams the video file from disk (a file object passed asdata, notread_bytes()) so large recordings don't get fully buffered into memory; it's still a single PUT rather than chunked with per-chunk retry, which would matter more for very large files or flaky connections.queue_video_for_uploadrejectspublish_atvalues less than 5 minutes out, andcut_videovalidateskeep_segments(sorted, non-overlapping,end > start) before touching ffmpeg — both fail fast with a clear error instead of wasting an upload/encode on bad input._run()catchesFileNotFoundErrorfor missing binaries (ffmpeg,ffprobe,auto-editor) and returns a clean "not found — is it installed and on PATH?" error through the normal returncode-check path, instead of crashing the tool call with a raw traceback.cut_videore-encodes at every cut (trim+concatfilter graph) rather than stream-copying, trading some encode time for frame-accurate boundaries — stream-copy cuts only land on keyframes, which would make Claude's semantic cut points imprecise.Default
category_idis"28"(Science & Technology) — override per call if a video fits better under"27"(Education) or"26"(Howto & Style).Scheduling logic (
_next_publish_slot) verified standalone: queuing 5 videos in a row againstYOUTUBE_POST_TIMES=10:00,17:00produced 2026-07-18 10:00, 2026-07-18 17:00, 2026-07-19 10:00, 2026-07-19 17:00, 2026-07-20 10:00 — correct 2/day spread with no double-booking.Full pipeline verified end-to-end 2026-07-18/19 (transcribe → tighten → queue → real OAuth upload), including three fixes found along the way:
tighten_videocalled bareauto-editorviasubprocess.run, which only exists in this project's.venv/bin, not on the MCP server's inheritedPATH.AUTO_EDITOR_BINnow resolves it explicitly (shutil.whichfirst, falling back to the venv's ownbin/next tosys.executable).macOS Screenshot/Screen Recording filenames insert a narrow no-break space (
U+202F) before AM/PM, visually identical to a normal space but byte-different — any retyped (vs. copy-pasted) path silently failedPath.exists()._resolve_video_path()now falls back to a whitespace-normalized filename match within the same directory across all four file-taking tools.The first real upload flickered and had audio skips. Two compounding causes, both in
tighten_video's defaultauto-editorinvocation: (1) macOS screen recordings are variable-frame-rate, and left to its default auto-editor timed the output off the source's average fps, landing on an arbitrary non-standard rate (52.41fps); (2) auto-editor's default bitrate (~1.4Mbps observed) was far too low for a high-res (3024x1898) screen recording with sharp text, producing visible compression-artifact flicker. Fixing the frame rate alone did not resolve it — the low bitrate was the dominant cause.tighten_videonow passes--frame-rate 60(TIGHTEN_OUTPUT_FPS) and--video-bitrate 10M(TIGHTEN_VIDEO_BITRATE, comfortably above the source's own ~4Mbps). Caught only after publishing — a private/scheduled upload with a bad encode isn't somethingyoutube-bridgecan fix or delete itself (no update/delete tool exists yet, unlikelinkedin-bridge); both bad uploads had to be deleted by hand in YouTube Studio before their scheduled publish time.
tighten_videodisabled 2026-07-19, the fps/bitrate fix above did not actually resolve the flicker. Two more videos processed through the fixedtighten_videostill showed the artifact and were unusable. Isolated the cause with an A/B test: the same recording, uploaded completely raw and unedited with no auto-editor pass at all, had no flicker. So the artifact comes from auto-editor's re-encode step itself, not the source capture and not YouTube's transcode, but the exact cause within auto-editor isn't identified yet.tighten_videonow returns an explanatory error instead of running, rather than silently producing unusable output.transcribe_video,cut_video, andqueue_video_for_uploadare unaffected. Roadmapped below to revisit once the real cause is found.Deliberately excluded from
mcpo_config.json, same rationale aslinkedin-bridge(see mcpo Notes below) — publishing tools stay MCP-only so the "confirm before calling" rule can't be bypassed by an HTTP client holding the proxy's API key.
slack-digest-bridge
Exposes one tool that fetches a Slack thread server-side and digests it with a local Ollama model, so the raw thread never has to land in the agent's own context the way it does through the hosted Slack connector:
digest_thread(thread_url, model="qwen2.5-coder:7b")— pass a Slack permalink to any message in the thread (a message's "Copy link" action) — works for both the parent message's link and a reply's link. Fetches the thread viaconversations.repliesand returnsDECISIONS/ACTION_ITEMS/BLOCKERSsections instead of the raw messages.
Every call is logged to slack_digest_log.jsonl (gitignored) as an audit
trail.
Setup
Create a Slack app at api.slack.com/apps → Create New App → From a manifest, pick the target workspace, and paste:
display_information: name: claude-tools-digest description: Read-only bot for Slack thread digests via claude-tools features: bot_user: display_name: claude-tools-digest always_online: false oauth_config: scopes: bot: - channels:history - channels:read - groups:history - groups:read settings: org_deploy_enabled: falseOn the app's OAuth & Permissions page, click Install to Workspace and approve. Copy the Bot User OAuth Token (
xoxb-...) shown after — ignore the separate App Credentials page (Client ID/Secret/Signing Secret); those are for building an external OAuth authorize flow, not needed for an app that only installs into your own workspace.Invite the bot to every channel you want digested — it can't read a channel's history until it's a member, even with the scopes granted:
/invite @claude-tools-digestin each one.Put the token in
~/.slack/.env(create it yourself):SLACK_BOT_TOKEN=xoxb-...then
chmod 600 ~/.slack/.env.Register the server with Claude Code:
claude mcp add slack-digest-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/slack_digest_bridge.pyRestart Claude Code / reload the window.
Notes
This exists because the hosted
claude_aiSlack connector already returns raw thread content into the agent's context by the time the agent sees it — it can't be the fetch path for a tool meant to keep raw messages out of context, so this bridge holds its own bot token and fetches server-side instead.Scopes are read-only on purpose (
*:history/*:read, nochat:write) — this bot never posts, it only ever reads threads it's been invited to.Message text can contain raw Slack IDs (
<@U12345>for a user,<#C12345|name>for a channel) since the bot doesn't haveusers:read— the digest instructions tell the model to carry those through as-is rather than guess a display name.conversations.repliesis called withlimit=200and no pagination — if a thread has more than 200 replies, only the first batch is digested and the response says so. Not built out further since no thread in this workspace is anywhere near that size yet.Verified end-to-end against a real message in
#claude-tools(conversations.repliesfetch, auth, and Ollama round trip all confirmed working), and separately against a synthetic 6-message thread (decision, action item with a raw<@U123>mention, achannel_joinsystem message, and a bare:+1:reaction) to check the filtering and extraction logic: the join event and the reaction were correctly dropped, the decision and action item were both extracted correctly with the mention preserved verbatim. One real miss in that same test — an open question in the synthetic thread ("not sure if we need a separate channel for the mobile team") wasn't picked up as aBLOCKER. Same caveat asollama-bridge's other triage tools: a 7B model's first pass, not a guarantee, re-read the thread yourself (slack_read_thread) for anything where a missed open question would actually matter.
image-bridge
Exposes one tool backed by Gemini's native image model
(gemini-2.5-flash-image), reusing the same GEMINI_API_KEY as
gemini-bridge:
generate_image(prompt, output_path, reference_image_paths=[])— generates an image and saves it tooutput_path(absolute path,.pngor.jpg). Passreference_image_paths(absolute paths to existing images) to guide style, character likeness, and composition against those references, e.g. an existing book cover, so a series of illustrations actually looks like one book instead of restarting the style from scratch on every call. Returns the saved path on success.
Every call is logged to log.jsonl (shared with gemini-bridge, gitignored)
as an audit trail.
Setup
Same
GEMINI_API_KEYasgemini-bridge(see its Setup section) — no separate credential needed.Install this project's dependencies (shared
.venv,google-genaiis inrequirements.txt):cd ~/git/claude-tools .venv/bin/pip install -r requirements.txtRegister the server with Claude Code:
claude mcp add image-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/image_bridge.pyRestart Claude Code / reload the window.
Notes
Uses the
google-genaiSDK directly (not thegeminiCLIgemini-bridgeshells out to) — the CLI has no image-generation flag, so this callsmodels.generate_contentagainstgemini-2.5-flash-imageitself, multimodal in: text prompt plus zero or more reference images as inline bytes, image out: the firstinline_datapart found in the response.Blocked on billing as of 2026-08-06. The free tier of the API key in
~/.gemini/.envhas a hard0request/token quota specifically forgemini-2.5-flash-image(confirmed via a live test call: clean HTTP round trip, clear429 RESOURCE_EXHAUSTEDresponse naming that exact quota) — every other quota on that key allows normal usage, this one model's free allowance is zero, not low. The code path itself is verified working end to end (request sent, error correctly parsed and returned, no image written), it just cannot succeed until billing is enabled on the Google AI Studio / Cloud project tied to that key.No image was actually generated or shipped anywhere as of this note — everything above the Blocked line is verified-working plumbing, not a verified-working image.
discord-bridge
Exposes four tools for reading, posting to, and managing Discord text channels under a bot's own identity, via Discord's REST API:
list_channels(guild_id)— lists the text/announcement channels in a server, so you can find a channel's numeric ID from its name before calling the other tools.read_channel(channel_id, limit=20)— returns the most recent messages (newest first), including attachment URLs.limitis capped at 100 (Discord's own per-request max).post_message(channel_id, content)— posts a message under the bot's identity. Posts live and immediately, visible to everyone in the channel — always confirm the exact channel and text with the user before calling this, never call it unprompted, same rule aslinkedin-bridgeandyoutube-bridge.create_channel(guild_id, name, topic="")— creates a new text channel in a server. Requires the bot to have Manage Channels permission. Creates a real, visible channel immediately — always confirm the exact server and channel name with the user first, never call it unprompted, same rule aspost_message.
Every call is logged to discord_log.jsonl (gitignored) as an audit trail.
Setup
Create an app at discord.com/developers/applications → New Application. On the Bot tab, click Reset Token to reveal a bot token, and turn on the Message Content Intent toggle under Privileged Gateway Intents — without it,
contentcomes back empty on messages the bot didn't author, even with the right channel permissions.On the OAuth2 → URL Generator page, check the
botscope, then under Bot Permissions check View Channels, Send Messages, Read Message History, and Manage Channels (needed forcreate_channel). Open the generated URL and invite the bot to your server. If the bot is already invited without Manage Channels, grant it the role permission directly in Server Settings → Roles instead of re-inviting.Put the token in
~/.discord/.env(create it yourself):DISCORD_BOT_TOKEN=...then
chmod 600 ~/.discord/.env.Register the server with Claude Code:
claude mcp add discord-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/discord_bridge.pyRestart Claude Code / reload the window.
Notes
Built after searching for an existing Discord MCP server came up empty — the ones found were either read-only (webhook posting, no channel reading), abandoned, or required more setup than just standing up a minimal bridge in this repo's existing style. Same pattern as
slack-digest-bridge: own bot token in a dotfile, stdliburllibonly, no new dependency inrequirements.txt.Uses Discord API version
v10(https://discord.com/api/v10), the current stable version as of this writing._format_discord_errorparses Discord's JSON error body (message+ numericcode) and adds a plain-English hint for the common failure modes:401(bad token),403(bot present but missing a permission, or Message Content Intent off),404(bot not actually in that server/channel, or a wrong ID).Deliberately excluded from
mcpo_config.json, same rationale aslinkedin-bridge/youtube-bridge—post_messagepublishes visibly under a real identity, so it stays MCP-only where the "confirm before posting" rule is enforced by Claude Code's own tool-call flow, not bypassable by an HTTP client holding the proxy's API key.list_channelshas been verified against a live server.create_channelis new and not yet verified against a live server/bot with Manage Channels permission — code path mirrors the other tools' verified request/error-handling pattern, but treat it as unverified until run once for real.
github-audit-bridge
Exposes three tools for auditing and fixing Dependabot coverage across every
repo on a GitHub account, backed by the gh CLI:
audit_dependabot_coverage(owner="wren-creator")— checks every non-archived repo underownerfor vulnerability alerts, automated security-fix PRs, and a.github/dependabot.yml(scheduled version-update config), in parallel. Returns one compact table plus a "needs attention" line listing repos with alerts off.list_open_work(owner="wren-creator")— lists open issues and open pull requests (fetched separately, not double-counted) for every repo that has any, skipping repos with nothing open.enable_dependabot(owner, repo)— turns on vulnerability alerts and automated security-fix PRs for one repo. Idempotent, safe to call on a repo that already has it on. Does not create adependabot.yml— that's a separate, per-repo config this tool doesn't write.
All three run entirely against the GitHub REST API through gh api — no LLM
calls, no repo cloning. Built to replace looping raw gh api calls by hand
for this kind of account-wide check, which burns a lot of turns doing the
same handful of lookups per repo.
Setup
gh auth loginwith a token scoped for repo security-events access (the account's existingghauth, if already logged in, is reused as-is — no separate credential file).Register the server with Claude Code:
claude mcp add github-audit-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/github_audit_bridge.pyRestart Claude Code / reload the window.
Notes
Built 2026-08-10 after a manual 41-repo Dependabot audit for
wren-creatorburned a lot of turns loopinggh apicalls one repo at a time. Verified against that same account:audit_dependabot_coveragereproduced the manual audit's numbers exactly (36 repos on, 5 off), andenable_dependabotwas used for real to turn on the 5 that were off.vulnerability-alertsreturns204/exit 0 when on and404/exit 1 when off —_check_repo_securityreads that offgh's own returncode rather than parsing a body, since the endpoint has no JSON body either way.automated-security-fixesdoes return a body ({"enabled": true/false}), parsed instead of trusting the HTTP status — an earlier version of the by-hand audit that led to this tool mistakenly treated any 2xx as "enabled" and got it wrong for repos with fixes actually off..github/dependabot.ymlpresence is checked via the exit code ofgh api repos/.../contents/..., not by inspecting stdout —gh api's 404 error body ({"message":"Not Found",...}) prints to stdout, not stderr, so an earlier draft that checked "is stdout non-empty" got a false "present" on every single repo, including ones with no config file at all. Caught by hand before this tool existed; the fix carried forward into_check_repo_securitydirectly.Excluded from
mcpo_config.jsonon purpose, same rationale aslinkedin-bridge/youtube-bridge/discord-bridge—enable_dependabotchanges real settings on a live GitHub repo. Keeping it MCP-only means that change only ever happens through Claude Code's own guarded tool-call flow, not an HTTP client holding the proxy's API key.
playwright-bridge
Exposes eight tools backed by Playwright for driving a real browser mid-session, e.g. to visually verify a UI/CSS fix or confirm a network dependency is (or isn't) actually being hit:
launch(browser="chromium", headless=True, viewport_width=0, viewport_height=0)— starts a browser, returns asession_id.browseris"chromium","firefox", or"webkit". Every request the page makes from this point untilclose()is recorded — seeget_requests.goto(session_id, url, wait_until="load")— navigates.wait_untilis"load","domcontentloaded","networkidle", or"commit". Returns JSON{"url", "title", "status"}.evaluate(session_id, script)— runs JavaScript in the page and returns the JSON-encoded result.scriptis an expression or function body, same as Playwright's ownpage.evaluate()— a script returning a Promise is automatically awaited. The general-purpose tool for reading DOM/computed style state or calling into a page's own JS (e.g. a dynamicimport('/js/whatever.js')) that the other tools don't have a shape for.screenshot(session_id, output_path, full_page=False, selector="")— saves a PNG tooutput_path(absolute path); read it back with Claude Code's ownReadtool to view it. Withselectorset, screenshots just that element.get_requests(session_id, url_contains="")— every network request made sincelaunch(), as JSON[{"url", "method", "resource_type", "status"}, ...]. Withurl_containsset, filters to matching URLs — e.g. confirm a CDN dependency was actually removed by checking zero requests contain"fonts.googleapis.com", without needing devtools open.click(session_id, selector, timeout_ms=5000)/fill(session_id, selector, text, timeout_ms=5000)— basic interaction, CSS or Playwrighttext=/role=selector syntax.close(session_id)— closes the browser and frees its resources.
Sessions live in memory for the lifetime of the server process (one per
Claude Code session) — close any session you're done with rather than
letting it leak. Every call is logged to playwright_log.jsonl (gitignored).
Setup
Install this project's dependencies (shared
.venv,playwrightis inrequirements.txt):cd ~/git/claude-tools .venv/bin/pip install -r requirements.txtInstall the browser binaries once — this is the actual fix for "waiting on a temporary install every session" (see Notes below). Start with Chromium alone; it covers most verification needs and, as of this writing, was already cached from earlier unrelated
npx playwrightusage, so it costs no download at all. Add Firefox/WebKit later (.venv/bin/playwright install firefox webkit) when on a connection that isn't metered/cellular — each is 100MB+ and this repo's own setup session stalled indefinitely trying to fetch Firefox over a cellular hotspot (see Notes):.venv/bin/playwright install chromiumRegister the server with Claude Code:
claude mcp add playwright-bridge --scope user -- \ ~/git/claude-tools/.venv/bin/python ~/git/claude-tools/playwright_bridge.pyRestart Claude Code / reload the window.
Notes
Built 2026-08-11 after verifying a font-loading fix for web3270 required ad-hoc
npx playwright install chromium firefoxmid-session — Firefox's binary took over 20 minutes to fetch through that ephemeral path and never finished, while Chromium happened to already be cached from an earlier unrelated session.npxre-resolves and re-fetchesplaywrightitself into a fresh~/.npm/_npx/<hash>/directory essentially every time it's invoked from a new working directory, so nothing about that install persists or speeds up the next one. A real dependency in this repo's shared.venv, installed once, does.Browser binaries cache at
~/Library/Caches/ms-playwright/, keyed by browser version, not per-project — this is the same cache directory Node'splaywright/@playwright/testpackages use on the same machine, so a Chromium version already fetched by either language's tooling is reused rather than re-fetched. Installing here once covers this bridge permanently; it does not need to be redone per project or per session.Uses Playwright's sync API (
playwright.sync_api), notasync_api— every tool function here is a plain synchronous function, consistent with the rest of this repo's bridges (subprocess-based, no asyncio event loop already running that would require the async variant instead).get_requestsexists specifically for the "did a network dependency actually get removed" class of check — the alternative (asking a human to open devtools, or scraping proxy/server logs) is slower and less precise than recording every request Playwright already sees pass through the page.evaluateis deliberately the most general tool rather than adding narrower ones (e.g. a dedicated "check computed font-family" tool) — it covers arbitrary DOM/JS inspection including calling directly into a page's own ES module exports via a dynamicimport(), which is what verifying the web3270 pipe-rendering fix actually needed (feeding a synthetic screen through the real client-siderenderLiveScreen()function, not a reimplementation of it).Not yet added to
mcpo_config.json—click/fillcan act on real, arbitrary web pages if pointed at one, closer totn3270-bridge'ssend_keys(interacts with a live target) than a pure read tool, and there's no immediate need for HTTP-proxied access from a non-MCP harness. Revisit if that need comes up.Verified end-to-end against a live
web3270Docker stack, calling this bridge's own functions directly (not just the ad-hoc Node/Playwright script that motivated building it):launch("chromium")started headless Chromium from the already-cached binary (no download),gotoloaded the app and returned{"url", "title", "status": 200},get_requests(..., url_contains="fonts.googleapis.com")correctly returned[],evaluateread backdocument.title,screenshotproduced a real, correctly rendered PNG of the app UI, andclosefreed the session cleanly.Getting a working
.venvfor this bridge was its own saga, worth recording since it's the exact pain this bridge exists to eliminate going forward: over a cellular hotspot connection,pip install playwrightfirst failed outright after ~15 minutes withConnectionResetError(silently — the driving command was piped throughtail, so pip's own non-zero exit code was masked bytail's exit 0; don't trust a piped command's reported exit status for a slow install, checkpip show <package>for a realVersion:line instead), then a retry with--retries 10 --timeout 60looked stuck for another ~15 minutes (same TCP connection, zero new data) before turning out to just be crawling at ~69 kB/s through a 42.5MB wheel (playwright's Python package bundles its own driver) — it finished on its own, unstuck, right as a second kill/retry was about to be triggered. Lesson: a slow-but-ESTABLISHEDconnection on a metered/cellular link can look identical to a dead one; check for forward progress (changing socket ports on retry, growing output) before killing and restarting something that's actually fine.Firefox and WebKit are deliberately not installed yet — each browser binary is 100MB+, and the connection issues above make that an expensive thing to force on a cellular connection.
playwright install firefox webkit(see Setup) is a same-day, low-risk follow-up once on a real connection;launch(browser="firefox"|"webkit")will fail with a clear Playwright "executable doesn't exist" error until then, not a silent wrong result.
mcpo proxy
Fronts gemini-bridge, tn3270-bridge, and repo-bridge with
mcpo, so tool-calling harnesses that
don't speak MCP natively (e.g. Ollama or llama.cpp-based agents) can call
these tools over plain HTTP/OpenAPI instead. linkedin-bridge,
youtube-bridge, discord-bridge, and github-audit-bridge are
deliberately excluded - see each one's Notes.
Setup
Install this project's dependencies (shared
.venv,mcpois inrequirements.txt):cd ~/git/claude-tools .venv/bin/pip install -r requirements.txtRun it, pointing at
mcpo_config.jsonand picking a real API key (not the placeholder below):.venv/bin/mcpo --port 8000 --api-key "your-own-key-here" --config mcpo_config.jsonEach server's tools are now live at
http://localhost:8000/<server-name>/<tool-name>(e.g.http://localhost:8000/repo-bridge/list_structure), with interactive OpenAPI docs per-server athttp://localhost:8000/<server-name>/docsand a combined spec athttp://localhost:8000/openapi.json. Every actual tool call requiresAuthorization: Bearer <api-key>; the docs/spec endpoints themselves are intentionally public (mcpo's default behavior).
Notes
linkedin-bridgeis excluded frommcpo_config.jsonon purpose.post_to_linkedin's "always confirm the exact text with the user first" rule is something Claude Code follows as an instruction, not something the proxy enforces - any HTTP client holding the--api-keycould otherwise trigger a real, public LinkedIn post with no confirmation step. Keeping it MCP-only means posting only ever happens through Claude Code's own guarded tool-call flow.Verified end-to-end: started mcpo with all three servers, confirmed a real
list_structurecall succeeds with the API key and returns 401 without it, and confirmed the (intentionally public)openapi.json//docsendpoints are reachable either way.
ollama-agent
A standalone chat loop (ollama_agent.py, not an MCP server itself) that lets
a local Ollama model actually call the tools mcpo fronts -
gemini-bridge, tn3270-bridge, repo-bridge - over plain HTTP. The DIY
alternative to routing through Open WebUI: no extra service, just a script
that turns mcpo's OpenAPI spec into Ollama's own function-calling tools
format and dispatches whatever the model calls.
Fetches each server's own
openapi.jsonfrommcpo(there's no single combined spec with real paths - see Notes) and converts it into Ollama'stoolsformat.Runs a normal
/api/chatloop: sends the conversation plustools, executes any tool call againstmcpo, feeds the result back, repeats until the model gives a plain-text final answer (capped at 8 tool-call rounds to avoid a runaway loop).Falls back to parsing a JSON-shaped call out of
message.contentwhen the model answers with one instead of populating Ollama's structuredtool_callsfield - see Notes, this is qwen2.5-coder's actual default behavior locally, not a rare edge case.
Setup
Have
mcpoalready running againstmcpo_config.json(see the mcpo proxy section above):.venv/bin/mcpo --port 8000 --api-key "your-key" --config mcpo_config.jsonPut the same key mcpo was started with in
~/.mcpo/.env(create it yourself):MCPO_API_KEY=your-key MCPO_URL=http://localhost:8000then
chmod 600 ~/.mcpo/.env.Run it:
.venv/bin/python ollama_agent.py # interactive REPL .venv/bin/python ollama_agent.py "your one-shot prompt"--model(defaultqwen2.5-coder:7b),--mcpo-url,--api-key, and--num-ctx(default 8192) override the defaults/dotfile.--list-toolsprints the loaded tools and exits without calling Ollama; the same list is available mid-conversation via/tools, in either interactive mode or as the one-shot prompt.
Every tool call (name, arguments, result) is logged to
ollama_agent_log.jsonl (gitignored).
Notes
mcpo's combined/openapi.jsonis just an index page linking to each server's own docs - itspathsobject is empty. The real per-tool schemas live at/<server-name>/openapi.json, one FastAPI sub-app per MCP server.fetch_mcpo_toolsreadsmcpo_config.jsondirectly for the server name list (the same configmcpoitself was started with) instead of trying to discover servers from the index page, then fetches each server's own spec and resolves its$refschemas.Confirmed live 2026-08-11 against both
qwen2.5-coder:7band:14b: neither actually populates Ollama's structuredmessage.tool_callsfield, despite both reportingtoolsin theirollama showcapabilities - they answer with a bare{"name": ..., "arguments": {...}}JSON object inmessage.contentinstead, sometimes with more than one such object printed back to back as plain text rather than one valid JSON value._fallback_tool_callsscanscontentfor every{and triesjson.JSONDecoder().raw_decode()from there, which stops at the first balanced close-brace and ignores anything before/after - handles markdown fences, multiple sequential calls, and trailing commentary without special-casing any of them.Also seen live: the model dropping a tool's
<server>__prefix (e.g. callingget_fileinstead of the registeredrepo-bridge__get_file)._fallback_tool_callsresolves a bare name back to its full one when exactly one server exposes it, and stays silent (falls through as unrecognized) rather than guessing when a bare name is ambiguous across servers.The system prompt explicitly warns against placeholder paths like
/path/to/repo- without it, a prompt that didn't spell out the repo path verbatim got a hallucinated placeholder path passed straight to a real tool call. Always state the real absolute path in the prompt; the warning reduces but doesn't eliminate this.num_ctxis set explicitly to 8192, same reasoning asollama_bridge.py's_call_ollama- Ollama defaults to 2048 regardless of a model's real context length, which would silently truncate the tool schemas and system prompt before the conversation even starts.:14bran noticeably slower than:7bon this (CPU-only, no local GPU) hardware -:7bis the default for interactive use; pass--model qwen2.5-coder:14bwhen latency isn't a concern.Verified end-to-end 2026-08-11:
repo-bridge__list_structureandrepo-bridge__get_file, both viaqwen2.5-coder:7bagainst a livemcpoinstance fronting this repo's own three servers - real tool calls dispatched, real results fed back, and a correct final answer synthesized from the actual file contents (DEFAULT_MODEL = "qwen2.5-coder:7b"read back out ofollama_bridge.pyitself).Same exclusions as the
mcpo proxysection above -linkedin-bridge/youtube-bridge/discord-bridge/github-audit-bridgearen't inmcpo_config.json, so this script can't call them either.
Roadmap
Add a third gemini-bridge tool for querying Gemini's larger context window on full files, not just diffs.
repo-bridge: expand
get_symbollanguage support beyond python/javascript/typescript/tsx/go — added rust, java, ruby, c, and cpp.repo-bridge:
get_symbolshould return all matches instead of just the first.tn3270-bridge: structured
read_screenmode (field positions, protected/unprotected, cursor location) alongside the plain-text dump, for when an agent needs to know where to type, not just what's shown.Front the MCP servers with an
mcpo(MCP-to-OpenAPI) proxy so tool-calling harnesses built on Ollama or llama.cpp — which don't speak MCP natively — can call these tools over plain HTTP.linkedin-bridgeexcluded on purpose (see mcpo Notes).linkedin-bridge: dig further into the post-truncation issue — root caused to LinkedIn's "little" text format (see Notes above); fixed by auto-escaping reserved characters before every post/update.
linkedin-bridge: add
update_linkedin_post/delete_linkedin_post— both work with thew_member_socialscope this app already has.Add
youtube-bridge: transcribe/tighten/cut a raw recording, then queue it for scheduled upload (YouTube's ownpublishAt, no daemon) with source files auto-moved toposted/. Upload/OAuth path verified end-to-end with a real upload (see Notes above).youtube-bridge: add
update_youtube_video/delete_youtube_video(mirroringlinkedin-bridge's pattern). Surfaced 2026-07-19 when a bad first upload (flicker/audio-skip from the fps bug, see Notes) sat privately scheduled on YouTube with no way to remove or replace it via MCP — had to be fixed by hand in YouTube Studio.youtube-bridge: find the actual cause of
tighten_video's flicker/ scanline artifact and re-enable it. Confirmed 2026-07-19 the fps/ bitrate fix didn't fix it, and that the artifact is specific to auto-editor's re-encode (raw unedited upload of the same recording had no artifact), but not yet which part of auto-editor's pipeline is at fault. Currently disabled, returns an error instead of running.linkedin-bridge: scheduled posting. LinkedIn's API has no server-side scheduled publish for personal profiles (that's a Company Page / Campaign Manager feature), so this would need Claude-side scheduling (a cron routine calling
post_to_linkedinat a set time) with pre-approved text, mirroringyoutube-bridge'spublishAtpattern but without native platform support. Surfaced 2026-07-21.youtube-bridge: chunked resumable upload with retry, for large files or flaky connections (current version sends the whole video in one PUT).
youtube-bridge: thumbnail upload (
thumbnails.set) once the core transcribe → cut → schedule path is verified end-to-end.linkedin-bridge: add read-back support (a tool that fetches a post's live content) once
r_member_socialis available. Blocked on LinkedIn, no ETA — that permission is currently closed to all new access requests, not just under heavy review, so there's nothing to do here until that changes.Add
ollama-bridge: aprefilter_difftool backed by a local Ollama model (qwen2.5-coder:7b), so routine diffs get a free/offline triage pass before spending areview_diff(Gemini) call — only escalate when it comes backFLAGGED. Verified end-to-end 2026-07-24.ollama-bridge: add
triage_log, a second tool that extracts just the root failure (FILE:/ERROR:/CONTEXT:) from a build/test log file via the same local model, instead of reading the whole raw log. Verified end-to-end 2026-07-24 against synthetic pytest logs (one failing, one clean).tn3270-bridge: panel-state abstractor. Added
read_panel_state, which extracts IBM message-ID lines (ICH408I, etc.) and unprotected fields with best-guess labels instead of a full screen dump. Surfaced 2026-08-06 brainstorming session on cutting agent token load. Unit tested against synthetic screen data only, not yet verified against a live host (see Notes above) — no test mainframe was reachable in this environment.ollama-bridge: add
triage_transcript, flagging likely restarts/ filler/dead-air spots in a youtube-bridge transcript instead of the agent reading the whole thing closely to find them. Doesn't replace transcribe_video's full transcript (cut_video's docstring is explicit that cut decisions need the agent reading the real thing), it points at where to look first. Surfaced 2026-08-06, ranked #2. Verified end-to-end against a synthetic transcript with a real Ollama call.image-bridge: blocked on billing. Built
generate_imageagainst Gemini's native image model (gemini-2.5-flash-image), code path verified end-to-end (clean request, correctly parsed429 RESOURCE_EXHAUSTEDerror), but the free-tier API key has a hard0quota for that specific model. Needs billing enabled on the Google AI Studio / Cloud project before a real image can be generated. Surfaced 2026-08-06 while building interior illustrations for a children's book manuscript.New slack-digest-bridge:
digest_thread, a local-model digest of a Slack thread (decisions/action-items/blockers) fetched server-side via its own read-only bot token, so raw thread content never has to land in agent context. Surfaced 2026-08-06, ranked #3. Verified end-to-end against a real message in #claude-tools and a synthetic multi-message thread (see Notes above) — one real miss found: an open question wasn't flagged as a BLOCKER, documented as a known first-pass gap.New discord-bridge:
list_channels/read_channel/post_messagefor a Discord server, since no existing Discord MCP server actually covered both reading and posting. Surfaced 2026-08-07. Not yet verified against a live server (see Notes above) — no Discord bot token was available to test against in this environment.Created
#britleys-corneron the Discord server (guild 961749187802824724) as a project idea inbox, random thoughts and ideas land there before they earn the right to become a real project. Introduced 2026-08-07.Measured the actual token savings from the
prefilter_diffescalation order, pulled straight fromollama_log.jsonl: 149 diffs reviewed by the local model, 147 of them (98.7%) came back clean and never escalated toreview_diff, only 2 needed the bigger model. That's roughly 90,000+ tokens of review output avoided on diff review alone. Surfaced 2026-08-08 while drafting a LinkedIn post on tiering AI work by cost, numbers cited there are these.New github-audit-bridge:
audit_dependabot_coverage/list_open_work/enable_dependabotfor checking and fixing Dependabot coverage across every repo on a GitHub account viagh api, no LLM calls. Surfaced 2026-08-10 after a manual 41-repo audit burned a lot of turns loopinggh apiby hand. Verified end-to-end against the realwren-creatoraccount — reproduced the manual audit's numbers exactly, andenable_dependabotwas used for real to fix the 5 repos found off.New playwright-bridge:
launch/goto/evaluate/screenshot/get_requests/click/fill/closefor driving a real browser mid-session — visual verification, computed-style/DOM inspection, and confirming a network dependency is or isn't actually being hit. Surfaced 2026-08-11 verifying a web3270 font-loading fix, where an ad-hocnpx playwright install chromium firefoxstalled on the Firefox binary for 20+ minutes and never finished — nothing about that install persists between sessions, so the next session would hit the same wall. A real.venvdependency, installed once, does persist (see Notes for the install itself turning into its own saga over a cellular hotspot). Verified end-to-end against a live web3270 Docker stack:launch+gotoloaded the app,get_requestscorrectly confirmed zerofonts.googleapis.comrequests,evaluateandscreenshotboth round-tripped real data. Chromium only for now — Firefox/WebKit installs deferred pending a non-metered connection.New
ollama_agent.py: a standalone chat loop giving a local Ollama model real tool access to the toolsmcpofronts (gemini-bridge,tn3270-bridge,repo-bridge), the DIY answer to "how can Ollama call these bridges" without routing through Open WebUI. Surfaced 2026-08-11. Verified end-to-end against a livemcpoinstance andqwen2.5-coder:7b, including two real model quirks caught and handled along the way: neither:7bnor:14bpopulate Ollama's structuredtool_callsfield, they answer with JSON text inmessage.contentinstead, and the model sometimes drops a tool's<server>__prefix or invents a placeholder path on an underspecified prompt. See theollama-agentsection above for the fixes for each.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityBmaintenanceLocal MCP server that wraps the headless Claude Code CLI as MCP tools, providing stateless access to Claude's coding capabilities through prompt-based interactions. It enables users to execute Claude Code commands with various prompt formats and structured outputs directly from MCP clients.3MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that lets Claude Code delegate mechanical tasks to a local LLM for summarization, classification, extraction, and drafting.99MIT
- FlicenseNot gradedqualityCmaintenanceA local MCP server that lets Claude Code start, resume, and wait for Codex app-server sessions while humans inspect live sessions from a terminal.
- AlicenseNot gradedqualityDmaintenanceA local MCP server that connects Claude Desktop to execute custom local code, such as generating custom greetings.8ISC
Related MCP Connectors
Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer
Augments MCP Server - A comprehensive framework documentation provider for Claude Code
Hosted Amazon Seller and Vendor MCP server for Claude, ChatGPT, Cursor, Codex, Gemini, Copilot.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/wren-creator/claude-tools'
If you have feedback or need assistance with the MCP directory API, please join our Discord server