>_ josetilabs.com
← blog

19 Oct 2026 · mcp, ia, agentes · 6 min

Ready made MCP servers to plug into your agent

I pointed a file server MCP at a process of mine and it answered with a list of 14 tools. The memory server answered with 9. Neither install touched the system: npx fetched the packages into a cache and ran them as a child process speaking JSON-RPC over stdin.

The part worth stating plainly: an MCP server is a program. It is not a daemon, not a service, it has no port. The agent writes one line of JSON to its stdin, the process writes one line of JSON back to stdout. Everything else is an SDK, and the SDK is optional.

the two servers I would keep

The modelcontextprotocol/servers repo holds the reference implementations maintained by the group that defines the protocol. Its README warns they are examples to learn from, not production components. Two are genuinely usable now.

The file server is the useful one. It takes the directories it may touch as command line arguments and stays inside them. It exposes read_text_file, write_file, edit_file, search_files, directory_tree, get_file_info, move_file and more, 14 in total. Write tools carry destructive hints, which lets the client ask for confirmation.

The memory server keeps what the agent learns in a graph of entities, relations, and observations, stored in a JSONL file. It has 9 tools: create_entities, create_relations, add_observations, search_nodes, open_nodes, read_graph, and three that delete. Its point is persistence across conversations.

the config, commented

Two servers in one config. What differs is the command and what goes in env.

json
{
  "mcpServers": {
    "files": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/[email protected]",
        "/path/the/agent/may/read"
      ]
    },
    "memory": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/[email protected]"],
      "env": {
        "MEMORY_FILE_PATH": "/home/you/.local/share/agent/memory.jsonl"
      }
    }
  }
}

The @2.0.0 on the package name is not decoration. Leave it off and you get the newest version on every install, so an upstream release breaks your agent and you changed nothing.

The MEMORY_FILE_PATH matters more than it looks. Without it the server writes to ~/.npm/_npx/<hash>/node_modules/.../dist/memory.jsonl, inside the npm cache. That works until the cache is cleared or the hash changes, and then the whole graph is gone. It happened on my first run: the file was four directories deep inside the cache.

the artifact

A bash script that speaks the protocol directly, so you can see what the agent sees before wiring up the agent. No SDK, no Python, no client.

bash
#!/usr/bin/env bash
# mcp-call.sh - talks to an MCP server over stdio, with no SDK and no agent.
#
# The protocol is JSON-RPC 2.0, one line per message, on the process's stdin and
# stdout. That is all of it. No Python and no client needed.
#
# Usage:
#   ./mcp-call.sh                     print the protocol
#   ./mcp-call.sh -- <command>...              list the tools
#   ./mcp-call.sh --tool NAME [--args JSON] -- <command>...   call one
#
# The "--" separates the server command from the options. Without it you cannot
# tell "npx -y package /path" from a tool named "-y", and any heuristic gets it
# wrong. That is why the tool name arrives in --tool rather than in a fixed slot.
#
# The detail that saves an hour of debugging: the server exits when stdin
# closes. Without the sleep after the last line, it reads the three messages and
# dies before answering the third. The pipe has to stay open.

set -uo pipefail

PROTOCOL='2025-06-18'
WAIT="${MCP_WAIT:-20}"
TOOL=""
ARGS='{}'

print_protocol() {
    cat <<'END'
An MCP server is a process speaking JSON-RPC 2.0, one line per message.

  {"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}}
  {"jsonrpc":"2.0","method":"notifications/initialized"}
  {"jsonrpc":"2.0","id":2,"method":"tools/list"}

Call a tool:
  {"jsonrpc":"2.0","id":2,"method":"tools/call",
   "params":{"name":"list_directory","arguments":{"path":"/tmp"}}}

Required order: initialize, notifications/initialized, then tools/list or
tools/call. The server exits when stdin closes.
END
}

case "${1:-}" in
    ""|"-h"|"--help") print_protocol; exit 0 ;;
esac

# Options come before the "--".
while [ $# -gt 0 ]; do
    case "$1" in
        --tool)   TOOL="${2:-}"; shift 2 ;;
        --args)   ARGS="${2:-{\}}"; shift 2 ;;
        --)       shift; break ;;
        *)        break ;;
    esac
done

if [ $# -eq 0 ]; then
    echo "missing the server command after the --" >&2
    exit 2
fi

INIT="{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"$PROTOCOL\",\"capabilities\":{},\"clientInfo\":{\"name\":\"mcp-call\",\"version\":\"0\"}}}"
READY='{"jsonrpc":"2.0","method":"notifications/initialized"}'

if [ -n "$TOOL" ]; then
    REQUEST="{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"$TOOL\",\"arguments\":$ARGS}}"
else
    REQUEST='{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
fi

# The { ...; sleep; } keeps stdin open after the last line.
OUTPUT="$({ printf '%s\n%s\n%s\n' "$INIT" "$READY" "$REQUEST"; sleep "$WAIT"; } \
  | timeout 120 "$@" 2>/dev/null)"

if [ -z "$TOOL" ]; then
    if printf '%s' "$OUTPUT" | grep -q '"result":{"tools"'; then
        printf '%s\n' "$OUTPUT" | grep '"result":{"tools"' \
          | jq -r '.result.tools[] | "  " + .name' | sort
        printf 'total tools: '
        printf '%s\n' "$OUTPUT" | grep '"result":{"tools"' | jq -r '.result.tools | length' | head -1
    else
        echo "no tools/list response. The server command failed or"
        echo "needs a different node version. Test the command on its own first."
    fi
else
    printf '%s\n' "$OUTPUT" | grep '"id":2' | jq -r '.result.content[]?.text // .error.message'
fi

what I measured

I ran both servers through the script on node v22.23.3, on 19 October 2026.

Diagram showing an agent sending JSON-RPC over stdin to an MCP server process, which replies with the tool list over stdout, with no network port involved
The whole path: one line of JSON goes in on stdin, one comes back on stdout, and no port is opened.

The file server returned its 14 tools, list_directory and write_file among them. My test call to list_directory came back with [FILE] init.jsonl, the only file in the directory I had passed. The memory server returned 9 tools, and create_entities wrote an entity that read_graph then read back in full.

Package sizes: 94,972 bytes unpacked for the file server and 57,615 for the memory server, 16 MB on disk each with dependencies. Both MIT licensed. The file server asked for node 24 or newer and ran on node 22 with a warning rather than failing.

how to verify

bash
chmod +x mcp-call.sh
./mcp-call.sh -- npx -y @modelcontextprotocol/[email protected]

The output lists the 9 tools and ends with the total. To call one:

bash
./mcp-call.sh --tool read_graph --args '{}' -- npx -y @modelcontextprotocol/[email protected]

jq sits in the middle of that path: it is what reads the response. Without it you see the raw JSON line, which is useful too.

when it goes wrong

The server answers initialize and dies. The most common case, and it is stdin closing. A pipe that ends right after the last message kills the process before it replies, because the server reads to end of file and exits. The script holds the pipe open with a sleep after the last line.

You see an EBADENGINE warning. The file server package asks for node 24 or newer and I tested on node 22. It worked, but on a locked down machine it might not. The fix is upgrading node.

The tool list comes back empty. Almost always a missing jq, or the server failing before the handshake. Run the command on its own and see whether the server identifies itself.

picking between them

Server Tools Keeps state in Good for
filesystem 2.0.0 14 nothing, only reads and writes file access bounded to a directory
memory 2.0.0 9 JSONL, via MEMORY_FILE_PATH remembering across conversations
fetch 2.0.0 1 nothing fetching a URL and converting to markdown
sequentialthinking varies nothing step by step reasoning, more token use

The first two cover what most agents need. An agent with a shell already has filesystem access, making the file server more of a boundary than a new capability, as discussed in the post on running a container as non-root. For file-based state you want to query, semantic search with sqlite-vec solves it in a way that is easier to audit.

questions that always come up

Do I have to install anything? No, if you use npx. The package is fetched into a cache and run as a child process. Installing globally works too, and then you lose track of which version is running.

Does an MCP server open a port? Not with the stdio transport, which is what both servers here use. Remote servers do listen on a socket, and then you have authentication and TLS in the middle.

Can I write my own? Yes, and it is less work than it sounds: the agent needs one line of JSON with a method and parameters. The reference servers show the envelope.

Can I run more than one at a time? Yes. Model Context Protocol treats each server as an independent block, and the client decides when to call which.

Pin a version or use latest? Pin it. Both packages shipped releases on the same day in the week I wrote this, and a tool renamed between versions breaks the agent with no warning.

sources

Server list and the note about them being reference implementations: github.com/modelcontextprotocol/servers, checked on 19 October 2026. Version, license, and tools: server-filesystem 2.0.0 and server-memory 2.0.0. The concepts of tool, resource, and prompt are in Understanding MCP servers, from the protocol team.

Read next

// By e-mail

Posts published here also go out by e-mail. Delivery has not started yet: whoever leaves an address now joins the first batch.

I store only your e-mail, the date and the record of this consent. No cookies, no trackers, nothing shared with third parties. Details in the privacy policy.