{"slug": "building-and-using-mcp-servers-4-things-i-learned", "title": "Building and using MCP servers: 4 things I learned", "summary": "A developer building the open-source Upwork job-finding skill upsweep and the eval tool doceval documented four lessons from working with Model Context Protocol (MCP) servers, including that tool selection is made by the host model reading tool names and descriptions rather than by code, and that local MCP servers exit within two seconds of the client process being killed. Testing doceval's server in Claude Code, the developer found the stdio link is a pair of pipes and that closing a server's input causes it to exit on its own, while PagerDuty engineers cited 20 to 25 tools as the practical sweet spot for a server's tool list.", "body_md": "I use Upwork's official MCP server from Claude Code, and doceval, my open-source eval tool, ships an MCP server of its own. This is how MCP works underneath, and four things I learned along the way. Where I tested something, I say how.\n\nMCP (Model Context Protocol) is an open standard for connecting AI apps to outside tools and data. A service wraps itself once as an MCP server, and any app that speaks MCP (Claude, ChatGPT, Cursor and many more) can connect to it without a custom integration.\n\nIf every app had to write its own integration for every service, 20 apps and 1,000 services would need 20,000 integrations. With MCP each side builds its half once: 20 clients plus 1,000 servers, 1,020 in all. It's the USB-C trick: one port, any charger. Like USB-C, the port says nothing about how good the charger is.\n\nUsually the host's model decides which tool to call. The server can be plain code (get a request, do the thing, return the result) or run its own model inside. The host can't tell the difference: a request goes in, a result comes out.\n\nI saw this while building [upsweep](https://dave8172-website.vercel.app/projects/upsweep), an open-source agent skill that finds Upwork jobs through Upwork's official MCP server. Claude, running inside Claude Code, reads Upwork's tool list, decides what to call and makes sense of what comes back. My own subscription pays for that model. Upwork pays nothing for it.\n\n`claude mcp add --transport http upwork https://mcp.upwork.com/mcp`. Upwork's server needs an account, so the first request is refused with \"sign in first\", the client finds Upwork's login server, and I sign in with OAuth. The token works only for that server. A server running locally on your own machine usually needs no sign-in.`tools/list`. The server replies with each tool's name, a plain-English description and the shape of its inputs (a JSON Schema).`tools/call` with the tool name and arguments. Upwork runs the search.\nThe messages are JSON-RPC: a method name plus parameters.\n\n```\n{\n  \"jsonrpc\": \"2.0\",\n  \"id\": 7,\n  \"method\": \"tools/call\",\n  \"params\": {\n    \"name\": \"find_jobs\",\n    \"arguments\": { \"query\": \"LLM evaluation\" }\n  }\n}\n```\n\n(Argument names simplified.)\n\nStep 3 surprised me most. In a normal agent loop, no code decides which tool gets called. The model decides, by reading names and descriptions. It never sees your code.\n\nSo write each description the way you'd brief a new colleague: what the tool does, when to use it, what comes back. Tool picking gets harder as the list grows and descriptions overlap. How many is too many depends on the model and the host. [PagerDuty's engineers](https://www.pagerduty.com/eng/lessons-learned-while-building-pagerduty-mcp-server/) call 20 to 25 the sweet spot, and their own server ships over 20. [doceval](https://dave8172-website.vercel.app/projects/doceval)'s needs two. One scores a single extraction against the expected values, the other runs a full eval over a labelled dataset.\n\nKeep results short too. A result goes into the model's context and usually stays there for the rest of the conversation. A tool that returns 50 pages makes the agent slower, dearer and more likely to lose the thread.\n\nUpwork's server runs on Upwork's machines. A local server is different: a program on your own computer that the app starts for you. I wanted to know exactly when it starts and stops, so I tested it with doceval's server in Claude Code: a test config passed to `claude -p`, and the process list checked every quarter of a second.\n\n`kill -9`, which gives it no chance to clean up. The server was gone within two seconds.\nThe link between them is a pair of pipes: the app writes requests into the server's input and reads replies from its output. When I closed a running server's input by hand, it exited on its own. That's how it learns the session is over, whether the app quit cleanly or crashed.\n\nBetween calls the server just waits, and each session starts its own copy. On a small machine, three open sessions means three copies of every local server in memory.\n\nA local server often needs a password or an API key, and the usual place for it is the `env` block in the app's MCP config. That's a plain-text file. Anything running as your user can read it, including a coding agent with file access, and a project's config file can end up in git.\n\nTwo things that help:\n\n`${VAR}` from your environment. I tested it with a config file passed to `claude --mcp-config`: a config with `\"SECRET\": \"${MY_TEST_SECRET}\"` handed the server the value from my shell. The config can then be shared, and the secret lives in one place outside it.\nStronger options exist, like the operating system's keychain or a secret manager that starts the server for you. I haven't tested those yet, so I'll stop at naming them.\n\nA job post, an email or a web page can contain text written to steer the model. That's prompt injection, and whatever a tool returns goes into the model's context.\n\nSo I approve every Upwork proposal myself. Upwork's MCP has a preview built in: the agent shows me the proposal, I confirm, and the preview expires after 15 minutes. Claude Code also asks before running a tool, unless you've allowed it.\n\nI got this wrong at first. I shipped upsweep with a rule that said \"drafts only\", because I read Upwork's terms as forbidding agent submission. They allow submitting, one approval per proposal, never scripted around. I corrected it in public: [Can an AI agent submit Upwork proposals?](https://dave8172-website.vercel.app/blog/can-ai-agent-submit-upwork-proposals)\n\nThe model never sees your code, only your tool names, descriptions and schemas. Write them like they are the code.\n\n`pip install \"doceval[mcp]\"`\n`npx skills add dave8172/upsweep`\nBoth are free and MIT licensed.", "url": "https://wpnews.pro/news/building-and-using-mcp-servers-4-things-i-learned", "canonical_source": "https://dev.to/dave8172/building-and-using-mcp-servers-4-things-i-learned-4agj", "published_at": "2026-10-05 15:38:55+00:00", "updated_at": "2026-10-05 15:47:49.529717+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "developer-tools"], "entities": ["Model Context Protocol", "Upwork", "Claude Code", "doceval", "upsweep", "PagerDuty", "Anthropic", "ChatGPT"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/building-and-using-mcp-servers-4-things-i-learned", "markdown": "https://wpnews.pro/news/building-and-using-mcp-servers-4-things-i-learned.md", "text": "https://wpnews.pro/news/building-and-using-mcp-servers-4-things-i-learned.txt", "jsonld": "https://wpnews.pro/news/building-and-using-mcp-servers-4-things-i-learned.jsonld"}}