simple-job
Provides web search capabilities via DuckDuckGo, used in the search_and_summarize tool to find and read relevant pages before summarization.
Allows using Ollama as the local model endpoint, sending prompts to its OpenAI-compatible API for tasks like summarization, extraction, and reformatting.
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., "@simple-jobDownload https://example.com/data.csv and verify the sha256 checksum matches the published one."
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.
mcp-simple-job
Hand a subtask to a local model on another machine, and verify the result before returning it.
Built for a specific shape of problem: the capable, metered assistant runs on the machine you sit in front of, while a perfectly good GPU box sits idle in the corner. Reading forty files, summarising a long page, pulling down a dataset — none of that needs the expensive model, and doing it in the assistant's context spends the one resource that is genuinely scarce.
Measured on the author's setup, against doing the same four jobs in-context: ~2.7x faster, and 6.1x less context spent, because the raw pages never enter it.
The one rule
A job must come with a check. A task that cannot state how it would be known to have worked is refused — not warned about, refused.
This is not distrust of the local model. It is that the caller cannot tell. When the assistant delegates a summary and gets back four hundred confident words, it has no independent way to know whether those words describe the document or something else. Delegating without a check adds a second place where a green light means nothing.
Checks are deliberately boring: nonempty, contains, regex, json_keys,
line_count, shell (your argv, exit 0 passes, output piped to stdin), and summary_of
(shorter than the source and not a copy of it).
Related MCP server: any-model-plugin
The four subtasks
tool | what it does | where the work happens |
| a plain prompt with your own check | the model host |
| text, or url/urls fetched first | worker machine (fetch + model) |
| search, read the top pages, summarise | search alternates hosts, rest on the worker |
| fetch a file, report bytes + sha256 | worker by default, any configured host |
simple_job_stats reports how all of them have actually gone, from a ledger row written
for every job.
Running it
Requirements
Node 18+ on the machine running the MCP server.
An OpenAI-compatible chat endpoint. llama.cpp's
llama-server, Ollama, vLLM, LM Studio, or a hosted API — anything that answersPOST /v1/chat/completions.Python 3.9+ on whichever machine does the fetching. Standard library only; no pip install, no node, no beautifulsoup.
SSH key auth to the second machine, if you use one. It is optional (see below).
How it is wired on the author's machines
Two computers:
A Mac mini — the daily workstation. 16 GB, usually a few gigabytes into swap. It runs Claude Desktop and therefore this MCP server.
A Pop!_OS box with an RTX 5070 Ti — the laboratory. It runs a 35B model called ornith under
llama-serveron port 8080, and is idle most of the day.
An SSH tunnel makes the remote model look local to the Mac:
ssh -N -o ServerAliveInterval=30 -L 127.0.0.1:8081:127.0.0.1:8080 pop-ospop-os is a ~/.ssh/config alias with a key and IdentitiesOnly yes, so the server can
reach it non-interactively with BatchMode=yes.
The MCP client entry is just:
{ "mcpServers": { "simple-job": { "command": "node", "args": ["/path/to/mcp-simple-job/index.js"] } } }Defaults do the rest: the model at 127.0.0.1:8081, the worker machine at pop-os, and
the ledger in an existing ~/Code/harness/ if there is one.
Nothing is ever called by hand. The assistant picks the tool — which is a harder problem than it sounds, and is covered below.
Running it on yours
Two machines, one running the client and one running the model:
{
"mcpServers": {
"simple-job": {
"command": "node",
"args": ["/path/to/mcp-simple-job/index.js"],
"env": {
"ORNITH_URL": "http://127.0.0.1:8081/v1/chat/completions",
"ORNITH_MODEL": "your-model-name",
"POP_HOST": "your-ssh-alias"
}
}
}
}One machine — everything local, no SSH anywhere:
{
"env": {
"ORNITH_URL": "http://127.0.0.1:11434/v1/chat/completions",
"ORNITH_MODEL": "qwen3:8b",
"SEARCH_HOSTS": "mac"
}
}SEARCH_HOSTS=mac is the switch that says "there is no second machine". Fetching,
downloading and searching all happen locally, and search simply has one rate budget
instead of two. Everything else behaves the same.
Environment variables
variable | default | what it does |
|
| the chat endpoint |
|
| model name sent in the request |
|
| SSH alias of the worker machine |
|
| machines to alternate searches across; set to |
|
| minimum gap between searches from one machine |
|
| how long a machine sits out after being throttled |
| next to | where search timing state is kept |
|
| the SQLite ledger |
|
| optional trace id to stamp on rows |
The ledger is optional. It is created on first use, and if it cannot be written the
jobs still run — logging is best-effort and never blocks work. HARNESS_TRACE is a hook
for the author's own tracing setup; ignore it and rows simply have a null trace id.
Getting it actually used
This is the part most people skip, and it is the part that decides whether any of the above matters.
A tool that nothing routes to is invisible no matter how well it works. The author has a separate MCP server that works perfectly and had zero calls in months, purely because nothing ever told the assistant to reach for it. Building a capability and routing to it are two different jobs, and finishing the first one feels like finishing.
Three ways to close that gap, cheapest first. Most people want the second one.
1. Do nothing, and see. Some clients read tool descriptions well enough that a sufficiently obvious request — "summarise these forty pages" — finds the tool on its own. Worth trying for a day before adding machinery. Watch whether it actually gets called.
2. Put a rule where your client keeps standing instructions. Claude Desktop project
instructions, a CLAUDE.md for Claude Code, .cursorrules, a custom GPT's instructions —
whatever your client reads on every turn. Something like:
There is a local model available through
simple-job. Use it when the material is not already in context and the job is mechanical: summarising pages or files, web search plus reading, fetching downloads, extraction and reformatting. It is free and does not spend context on the source.Do it yourself when the text is already in context, when the job needs interpretation rather than transcription, or when being right matters more than being checkable. Never delegate judgment calls, code that must be correct, or file edits.
Every job must carry a check — the server refuses work it cannot verify.
Spend as many words on when not to delegate as on when to. The failure mode of routing a delegation tool is over-delegation, and an assistant that ships everything downhill will hand you faithful transcription where you wanted judgment.
3. Wire it into a router, if you have one. If your setup already matches situations to tools, add an entry for "bulk reading or fetching material not yet in context". The advantage over a standing instruction is that it is measurable — you can count whether it fired when it should have. A standing instruction either works or it does not, and nothing records which.
What not to send it
Anything where "looks right" is the only test. Judgment calls. Code that must be correct. Editing files.
And one boundary found by measurement rather than taste: a small local model transcribes faithfully but does not interpret. In testing it reproduced a source's ambiguous phrasing verbatim instead of resolving what it meant, and summarised a repository's star count as though it were part of a bug report. Send it transcription. Keep interpretation.
Notes from building it
Everything below is a measurement, not an opinion. The numbers are in the code comments too.
Thinking is off by default
Reasoning models emit their deliberation and their answer from the same token budget. On one summarising job run three times identically, two of the three spent 5,500–6,000 characters thinking, hit the ceiling, and returned an empty answer with HTTP 200.
Raising max_tokens did not fix it. reasoning_effort: "low" did not fix it. A
/no_think system tag did not fix it. Only chat_template_kwargs: {enable_thinking: false} did, and the same job then answered in 258 tokens. Pass think: true for a job
that genuinely needs deliberation, and raise max_tokens with it.
Search is rationed, and DuckDuckGo lies about why
DuckDuckGo does not rate-limit politely:
a served query is HTTP 200, ~28 KB, ten result links
a refused one is HTTP 202, ~14.2 KB, and its text reads "Please complete the following challenge... Select all squares containing a duck"
It is a captcha flag on the IP, not a timed limit, and spacing does not clear one. After four minutes of silence, six queries at 30 s spacing from one machine and six at 15 s from the other were 0 for 12. Polling every 5 s during a block never recovered in 162 s — retrying feeds it. The flag decayed on its own in roughly twenty minutes.
So searches are spaced, alternated across machines (two IPs are two budgets), and a
throttle is reported as throttled, never as "no results". Those mean opposite things.
Reading follows the question
A long page gets cut to fit the model's window, and cutting from the top answers the wrong question silently. Asked about "sparse gating and load balancing" in a 40,063-character page with an 8,000-character window, the first version returned a fluent summary of the article's opening — in which "load balancing" never appears (it starts at character 16,181) and "sparse" never appears (14,671).
So focus steers the window: a head for context, then the passages around each named
term, one window guaranteed per term before any term gets a second. Two earlier
versions were not enough — substring matching found "load" inside "download" and reported
42 hits of noise, and taking passages in document order spent the budget before reaching
character 16,181.
If the page never uses those words, the call returns ok:false with focus_not_found.
Zero hits is a better answer than a plausible summary of other material.
A page that is mostly script is refused
One site served 68,896 bytes containing 40 characters of text ("Loading..."), which the
original if not text guard passed — so the shell went into a summary as source material
and the model wrote a confident benchmark figure citing it.
Two tests now, because either alone is fooled: an absolute floor, and a text-to-bytes ratio that only condemns a page which is also short. A GitHub issue is 290,000 bytes of markup around 3,896 characters of real discussion, and the ratio alone threw it away.
Citations are numbered so they can be checked
search_and_summarize numbers the pages and asks for [1], [2] rather than urls. Asked
for urls, the model attributed a figure it had read in a blog to a documentation page —
the fact was real and in the material, the attribution was not, and summary_of cannot
see that because a mislabelled bullet is the right length and is not a copy.
A url is a long opaque string to copy correctly. An integer is not, and it can be range-checked against the pages actually read — which the code does, failing the call on an out-of-range number and counting bullets with no source at all.
Which machine is faster?
Verified by identical sha256 on both sides:
client machine | worker machine | |
ssh round-trip | — | ~185 ms per call |
fetch 4 pages | ~1.7 s | ~2.0 s |
download 20 MB | 17.5 MB/s | 12.6 MB/s |
The client machine was faster at both. Speed is not the reason to send work to the worker. The reasons are that it holds the model, that it is idle while the other machine is in use, and that a second machine is a second search budget. Choose a download's host by where the file is needed, not by throughput.
Tests
node test_e2e.mjs # 24 assertions, spawns the real server over JSON-RPC
node --test test/simple-job.test.mjs # 19 unit assertions on the checksThe end-to-end suite spawns the actual server the way a client would, against a throwaway ledger. An in-process test would not catch a PATH or environment bug, and those are exactly the ones that only appear after a restart.
Changes to index.js take effect when the client next starts the server. pop_agent.py
is re-read on every call, so changes to fetching, searching and downloading are live
immediately.
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
- FlicenseAqualityCmaintenanceLocal MCP server that enables delegating low-risk tasks like summarization or code patches to a low-cost model, with the main agent reviewing results.2
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to delegate tasks, run adversarial reviews, and manage background jobs across multiple models and providers via anymodel_* tools.Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables Claude Code to delegate mechanical tasks (summaries, boilerplate, reformatting) to local models running in LM Studio.1MIT
- AlicenseNot gradedqualityAmaintenanceDelegates replaceable grunt work (boilerplate, formatting, translation, long-document summarizing) from a premium agent to cheap models behind a local LiteLLM proxy, auto-routing each task by type. Delegated calls run in a separate process, so the subscription session and the API credentials never share an environment.MIT
Related MCP Connectors
Verifies AI agent work end to end: real artifacts and outcomes checked, not self-reported success.
LLM chat, text summarization and AI image generation
Free OpenAI-compatible inference with signed provenance receipts and 3 focused MCP tools.
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/MikeyBeez/mcp-simple-job'
If you have feedback or need assistance with the MCP directory API, please join our Discord server