Running an MCP Server Without Keeping the Desktop App Open
If you have wired a local search tool into an AI client, you may have hit this: the agent works, but only while a desktop window sits open in the background. Close the window and the agent loses its memory. Leave it open and you are donating a couple of gigabytes of RAM to a window you are not looking at.
That was true of LocalSynapse until 2.18.0. This post is about why it happens, what changed, and how to verify the arrangement on your own machine.
Why the connector ends up trapped inside the app
MCP servers usually communicate over stdio — standard input and output. The client launches the server as a child process and they talk through that pipe. No network port, no daemon, no listener to secure. It is a good design: the lifetime of the server is exactly the lifetime of the conversation that needs it.
The trap appears when a product has both a desktop UI and an MCP server, and the easiest way to start the server is from inside the already-running application. That works immediately, and it quietly inherits one property nobody chose: the connector is now a child of the window. Close the window, the child dies, the agent goes blind.
Users then discover the workaround before the developer does — leave the app running, minimised, forever. And that is where the cost lands. On our own machine, an idle desktop instance that had loaded the semantic model sat at roughly 1.9 GB of working set with nothing to do. On a laptop that is not just memory; it is a process that keeps waking the disk and drawing power for a window nobody has looked at since morning.
What changed
From 2.18.0 the connector is launched by your MCP client, not by the app. The client reads its config, starts the LocalSynapse executable with the mcp argument as a process of its own, and speaks stdio to it. The desktop window is no longer part of the chain.
Concretely, the process model went from this:
Claude Desktop
└─ (nothing — unless you remember to open the app)
LocalSynapse.exe ← desktop window, must stay open
└─ connector ← dies when the window closes
to this:
Claude Desktop
└─ LocalSynapse.exe mcp ← started by Claude, its own process
exits when Claude exits
LocalSynapse.exe ← optional. Open it if you want the UI.
Closing it changes nothing for Claude.
The configuration
On Windows, in %APPDATA%\Claude\claude_desktop_config.json:
{
"mcpServers": {
"localsynapse": {
"command": "C:\\Program Files\\LocalSynapse\\LocalSynapse.exe",
"args": ["mcp"]
}
}
}
On macOS, in ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"localsynapse": {
"command": "/Applications/LocalSynapse.app/Contents/MacOS/LocalSynapse",
"args": ["mcp"]
}
}
}
Two details are worth stating because they are the usual causes of a config that looks right and does nothing. The backslashes in the Windows path are doubled, because JSON treats a single backslash as an escape character. And Claude Desktop reads this file at startup only — quit it completely, from the tray as well, and reopen it. A window close is not a quit.
Verifying it, rather than hoping
Three checks, in increasing order of usefulness.
The process list. With your client open and the desktop window closed, you should still see a LocalSynapse process, and its parent should be the client. That single observation is the whole claim of this post.
The tool list. Ask the agent what tools it has available. LocalSynapse currently exposes nine, and if the agent can name them the handshake completed.
An actual search. The definitive check, because it exercises the index and not just the connection. Ask something that requires reading your files, then ask which tool was called and what it returned. This last step matters more than it sounds — an agent that cannot find your files will often answer anyway, from general knowledge, in a tone indistinguishable from having looked.
What a response actually contains
Since 2.19.0 a search result is not a list of file teasers. It is a set of documents with passages under them, plus a statement of how the query was interpreted. Trimmed to its shape:
{
"query": "contract review comments",
"purpose": "answer",
"documents": [
{
"fileId": "…",
"path": "…/2026/agreements/supply-draft-v8.docx",
"displayName": null,
"modifiedAt": "2026-07-28",
"rankingScore": 0.1101,
"passages": [
{ "at": "chunk 0 chars 0-600", "text": "…" },
{ "at": "chunk 3 chars 0-600", "text": "…" }
],
"noPassageReason": null
}
],
"request": {
"asked": "contract review comments",
"readAs": {
"terms": { "used": ["contract", "review", "comments"], "dropped": [] },
"matchExpression": "…",
"scope": { "kind": "all", "time": "all", "location": "all" },
"unit": { "purpose": "answer", "purposeDeclared": true }
},
"notConstrained": [
{ "axis": "kind", "options": [
{ "value": "document", "count": 11 },
{ "value": "email", "count": 9 } ],
"howToConstrain": "…" }
],
"conflict": null
},
"basis": {
"mode": "Smart",
"documentsFound": 20,
"documentsReturned": 20,
"ranking": { "howToRead": "…" }
}
}
The parts worth pointing at:
passages— several excerpts per document, each with its position, rather than one truncated teaser. That is what makes a single call enough to answer from.purpose— the caller says what it is doing. Locating documents and answering a question need different amounts of material, so the response is sized accordingly rather than guessed at.request.readAs— how the query was actually read: which terms ran, which were dropped as stop words, what expression executed. Search engines normally hide this, which means their interpretation cannot be questioned.notConstrained— the axes that were not filtered, with real counts, and how to filter them. This is the connector asking a question back: these results split across documents and email, do you want one of them?conflict— populated when the interpretation does not hold up, for example when every word of the query was a stop word. In that case the response says so instead of returning a plausible-looking list.
Two clients at once
A reasonable worry with a per-client process is what happens when you run two. Claude Desktop in one window, another agent elsewhere, both pointed at the same index.
Search is unaffected: each client gets its own connector process and full search including semantic results. Indexing is not duplicated. Exactly one process maintains the index at a time, elected among whichever processes are running, with the desktop app taking that role when it is open. So the second client is a reader, not a second indexer, and your files are never processed twice.
The cost to be aware of is memory: the semantic model is loaded per process that needs it, so two connectors doing semantic search will hold it twice. That is also why the model is released as soon as there is nothing left to index rather than held indefinitely.
If you are on an older build
If closing the app window still cuts off your agent, you are on something older than 2.18.0 and updating is the fix. It is worth checking rather than assuming, because the failure is quiet: the agent does not announce that it lost a tool. It simply starts answering from general knowledge, which reads exactly like an answer until you check it against your files.