[{"data":1,"prerenderedAt":780},["ShallowReactive",2],{"news-item-\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files":3},{"id":4,"title":5,"body":6,"category":765,"created by":768,"date":769,"description":770,"extension":771,"meta":772,"navigation":773,"path":774,"sections":775,"seo":776,"stem":777,"thumbnail":778,"__hash__":779},"content_en\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files.md","MCP in practice: building an AI agent that organizes files",{"type":7,"value":8,"toc":748},"minimark",[9,14,18,27,35,38,41,45,48,51,70,86,90,93,97,103,106,109,126,130,136,139,146,150,156,160,165,171,178,181,185,192,201,227,233,242,249,252,267,271,280,305,310,316,319,325,330,340,346,353,359,362,368,372,385,389,395,399,405,409,415,419,425,428,432,437,447,450,456,464,478,481,485,491,494,498,504,507,511,517,520,524,530,534,537,561,569,575,578,582,585,588,592,595,605,612,625,635,643,646,649,655,658,665,669,672,676],[10,11,13],"h2",{"id":12},"introduction","Introduction",[15,16,17],"p",{},"An LLM can answer questions, but it does not know by itself how to read a folder or call an internal API. Every capability needs an integration point to describe the tool, validate the input, call the real system and bring the result back into the conversation. If every AI application wires itself to every system, n applications and m systems can produce up to n × m adapters to write and maintain.",[19,20],"img",{"className":21,"alt":24,"src":25,"style":26},[22,23],"block","mx-auto","","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage3.png","width: 100%;",[15,28,29],{},[30,31,34],"em",{"className":32},[33],"caption","An overview of how MCP connects an AI application to external systems: API, Slack, database, GitHub, Gmail, file system.",[15,36,37],{},"The Model Context Protocol (MCP) sets a common contract between the two sides. An MCP Server publishes its capabilities along with input\u002Foutput schemas; any compatible AI application can discover and call them over the same protocol. Business logic and the real API calls live on the server. The host decides which tools are handed to the model and how confirmation is requested; the server still has to check permissions for the operations it receives. The MCP documentation compares this standardized connection to USB-C.",[15,39,40],{},"This article tests that idea with a demo that actually runs: an agent uses two MCP tools to organize 500 mock files by extension and modification date, without reading what is inside the files.",[10,42,44],{"id":43},"what-mcp-is-a-protocol-not-an-ai-model","What MCP is: a protocol, not an AI model",[15,46,47],{},"MCP is an open specification for the messages exchanged between an AI application and a program that supplies data or actions. It standardizes how the two sides negotiate a version, publish capabilities, describe inputs with a schema, call tools, and return results. MCP does not replace business APIs, does not grant permissions by itself, and does not decide which tool should be called.",[15,49,50],{},"In the file-organizing demo, one turn plays out like this:",[52,53,54,58,61,64,67],"ul",{},[55,56,57],"li",{},"The user types a request into the AI application, which is the MCP Host.",[55,59,60],{},"The host creates an MCP Client dedicated to the File MCP Server and establishes the connection.",[55,62,63],{},"The client fetches the tool list from the server through tools\u002Flist; the host chooses which tools go into the request sent to the model.",[55,65,66],{},"When the model picks a tool, the host sends tools\u002Fcall through the client. The server validates the input, operates on the file system, and returns the result.",[55,68,69],{},"The host feeds the tool result back into the conversation so the model can decide the next step.",[15,71,72,73,77,78,81,82,85],{},"The three roles in the architecture therefore have fairly concrete boundaries. ",[74,75,76],"strong",{},"The host"," is the application the user has open, such as Claude Desktop, Claude Code or an IDE. ",[74,79,80],{},"The client"," is the protocol component the host creates. Each client keeps a 1-1 connection with one server. ",[74,83,84],{},"The server"," is the program that publishes capabilities and does the real work.",[10,87,89],{"id":88},"why-use-mcp-instead-of-a-custom-api-integration","Why use MCP instead of a custom API integration",[15,91,92],{},"The most visual comparison lies in the number of integrations required.",[19,94],{"className":95,"alt":24,"src":96,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage13.png",[15,98,99],{},[30,100,102],{"className":101},[33],"Comparing integration points: custom integration needs up to n × m adapters; MCP brings the cost close to n + m.",[15,104,105],{},"With the custom approach, each AI application may need one adapter per API. In the simple model, n applications and m APIs create up to n × m integration points. With MCP, each system publishes one server following a shared standard, and each host implements the client once. When the components are genuinely reusable, the integration cost gets closer to n + m. This is a way of estimating architecture, not a promise that every API needs exactly one server.",[15,107,108],{},"A few concrete benefits come with it:",[52,110,111,114,117,120,123],{},[55,112,113],{},"The server defines the schema, validates input, and executes business logic once for every host that connects to it.",[55,115,116],{},"The host, or the model's adapter, converts the tool contract and the results into the format the provider requires.",[55,118,119],{},"One MCP Server can serve many MCP Clients instead of wiring API-calling code into one specific agent framework.",[55,121,122],{},"The client discovers tools or resources at runtime instead of hard-coding the whole list into the prompt.",[55,124,125],{},"The model only sees the tools the host chooses to put into the request. The confirmation UI and the permission policy depend on the host, the transport, and the server configuration.",[10,127,129],{"id":128},"a-worked-use-case-an-inbox-nobody-cleaned-for-a-year","A worked use case: an inbox nobody cleaned for a year",[15,131,132,135],{},[74,133,134],{},"Picture a folder nobody has cleaned for a whole year — it might be a real folder on your own machine: screenshots, Word files from meetings, PDF invoices, revenue reports, a few Excel files, all mixed together."," Without an agent, reorganizing that pile leaves two options: drag and drop every file by hand, or write a script that classifies by fixed rules (if the extension is X, move it into folder Y). That script has to be edited every time a new file type appears, and it is hard-wired to a single folder — using it on another folder means editing the code again.",[15,137,138],{},"The demo in this article recreates exactly that situation: a script generates 500 mock files into a single folder, with each file's modification date spread randomly across the last 365 days, simulating a year without any cleanup.",[15,140,141,142,145],{},"The user gives ",[74,143,144],{},"one natural-language command plus the path to organize."," The agent lists the files and calls move_files; the server itself picks the destination folder from the extension and the modification date, filing each file by type and then by year\u002Fmonth\u002Fday.",[19,147],{"className":148,"alt":24,"src":149,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage4.png",[15,151,152],{},[30,153,155],{"className":154},[33],"storage\u002Finbox before processing: 500 mock files, many extensions mixed.",[10,157,159],{"id":158},"architecture","Architecture",[19,161],{"className":162,"alt":24,"src":163,"style":164},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage11.png","width: 50%;",[15,166,167],{},[30,168,170],{"className":169},[33],"The call flow in the demo: User → Custom MCP Host\u002FAgent (Gemini + MCP Client) → File MCP Server (list_files, move_files) → Local File Storage.",[15,172,173,177],{},[174,175,176],"code",{},"client.ts"," is the custom host\u002Fagent application. The mcpClient variable holds the protocol connection to the server; Gemini picks the tool and arguments. The File MCP Server lists or moves files, and the file system stores the real data.",[15,179,180],{},"The server has no tool that reads file contents, so the contents of an invoice, a photo, or a document are never sent to the model. The model sees only the file name, extension, and modification date.",[10,182,184],{"id":183},"building-the-mcp-server-two-tools-list_files-and-move_files-no-file-content-access","Building the MCP Server: two tools, list_files and move_files — no file-content access",[15,186,187,188,191],{},"The server registers two tools. The first snippet keeps the schema declaration and the handler of ",[174,189,190],{},"list_files","; the helpers that validate paths and read metadata live in the full source.",[193,194,199],"pre",{"className":195,"code":197,"language":198},[196],"language-text","\u002F\u002F server\u002Fserver.ts\n\nimport { McpServer } from \"@modelcontextprotocol\u002Fserver\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\u002Fserver\u002Fstdio\";\nimport { z } from \"zod\";\nimport fs from \"node:fs\u002Fpromises\";\nimport path from \"node:path\";\n\nconst server = new McpServer({ name: \"file-organizer\", version: \"1.0.0\" });\nserver.registerTool(\"list_files\", {\n  description: \"List direct child files and their modification dates.\",\n  inputSchema: z.object({ folder: z.string() }),\n}, async ({ folder }) => {\n  const root = resolveFolder(folder);\n  const entries = await fs.readdir(root, { withFileTypes: true });\n  const files = await Promise.all(entries.filter((entry) => entry.isFile()).map(async (entry) => ({\n    name: entry.name,\n    extension: extensionOf(entry.name),\n    modified: isoDateOf((await fs.stat(path.join(root, entry.name))).mtime),\n  })));\n  return { content: [{ type: \"text\", text: JSON.stringify(files) }] };\n});\n","text",[174,200,197],{"__ignoreMap":24},[15,202,203,206,207,210,211,214,215,218,219,222,223,226],{},[174,204,205],{},"move_files"," takes a batch. ",[174,208,209],{},"to_folder"," is optional: leave it out and the server derives ",[174,212,213],{},"\u003Cextension>\u002F\u003Cyear>\u002F\u003Cmonth>\u002F\u003Cday>"," from the file's own modification date, so the default classification holds even when the request never spells it out. ",[174,216,217],{},"moveOneFile"," in the full source performs the validation, creates a hard link under a no-overwrite scheme and then deletes the source; one failing entry does not stop the remaining ones. ",[174,220,221],{},"dry_run"," previews the whole batch without touching the file system, and the real result also carries a ",[174,224,225],{},"summary"," line counting the files moved.",[193,228,231],{"className":229,"code":230,"language":198},[196],"server.registerTool(\"move_files\", {\n  description: \"Move files under organized\u002F without overwriting existing paths.\",\n  inputSchema: z.object({\n    folder: z.string(),\n    moves: z.array(z.object({ name: z.string(), to_folder: z.string().optional() })).min(1),\n    dry_run: z.boolean().optional(),\n  }),\n}, async ({ folder, moves, dry_run }) => {\n  const root = resolveFolder(folder);\n  const results = [];\n  for (const { name, to_folder } of moves) {\n    try {\n      const message = await moveOneFile(root, name, to_folder, dry_run);\n      results.push({ name, to_folder, ok: true, message });\n    } catch (error) {\n      results.push({ name, to_folder, ok: false, error: String(error) });\n    }\n  }\n  return { content: [{ type: \"text\", text: JSON.stringify({ results }) }] };\n});\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n",[174,232,230],{"__ignoreMap":24},[15,234,235],{},[236,237,241],"a",{"href":238,"rel":239},"https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer\u002Fblob\u002Fmain\u002Fserver\u002Fserver.ts",[240],"nofollow","Full source of the server",[15,243,244,245,248],{},"Those last two lines are where the server actually goes live: ",[174,246,247],{},"StdioServerTransport"," declares that the server speaks over its own process's stdin\u002Fstdout, so a host only needs to know how to spawn the process to connect.",[15,250,251],{},"Two design points are worth noting. First, a file-organizing MCP server does not need — and should not have — the ability to read the contents of a user's files; the name, the extension, and the modification date are enough to classify. Second, folder is any absolute path, not the name of a fixed subfolder inside the project directory — so it can organize any real folder on the machine.",[15,253,254,255,258,259,262,263,266],{},"Because folder is that open, the server also reads the ",[174,256,257],{},"ALLOWED_ROOTS"," environment variable: a list of absolute paths, separated by ",[174,260,261],{},";"," on Windows and ",[174,264,265],{},":"," on macOS\u002FLinux, and folder must sit inside one of those roots. If it is not set, the server keeps the demo's default behavior of accepting any absolute path — acceptable with mock data, but it should be set when pointing at real data.",[10,268,270],{"id":269},"building-a-custom-hostagent-with-gemini","Building a custom Host\u002FAgent with Gemini",[15,272,273,275,276,279],{},[174,274,176],{}," combines the custom host\u002Fagent, Gemini and an MCP Client in the same process. Gemini picks the tool; the MCP Client only handles the protocol connection to the File MCP Server. ",[74,277,278],{},"The Gemini API had a free tier"," at the time of verification, with quotas depending on the model and the account.",[15,281,282,283,288,289,292,293,296,297,300,301,304],{},"Create an API key at ",[236,284,287],{"href":285,"rel":286},"https:\u002F\u002Faistudio.google.com\u002F",[240],"Google AI Studio",", then save it to ",[174,290,291],{},"GEMINI_API_KEY"," in ",[174,294,295],{},".env",". ",[174,298,299],{},"MODEL"," is optional; the code defaults to ",[174,302,303],{},"gemini-2.5-flash",".",[19,306],{"className":307,"alt":24,"src":308,"style":309},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage6.png","width: 80%;",[15,311,312],{},[30,313,315],{"className":314},[33],"Creating a Gemini API key in Google AI Studio: naming the key and selecting the Gemini API project.",[15,317,318],{},"Next, the custom host\u002Fagent creates an MCP Client and connects to the server over stdio. The path computation and the tool-call limit live in the full source.",[193,320,323],{"className":321,"code":322,"language":198},[196],"\u002F\u002F client\u002Fclient.ts\n\nimport { Client } from \"@modelcontextprotocol\u002Fclient\";\nimport { StdioClientTransport } from \"@modelcontextprotocol\u002Fclient\u002Fstdio\";\n\nconst transport = new StdioClientTransport({\n  command: process.execPath,\n  args: [\"--import\", \"tsx\", serverPath],\n  env: { ALLOWED_ROOTS: process.env.ALLOWED_ROOTS ?? \"\" },\n});\nconst mcpClient = new Client({ name: \"file-organizer-agent\", version: \"1.0.0\" });\nawait mcpClient.connect(transport);\n",[174,324,322],{"__ignoreMap":24},[15,326,327],{},[30,328,329],{},"Verification environment on 2026-08-27: Windows 11, Node.js 24.15.0, @modelcontextprotocol\u002Fclient 2.0.0, @modelcontextprotocol\u002Fserver 2.0.0, @google\u002Fgenai 2.17.0, Zod 4.4.3, tsx 4.23.12 and TypeScript 7.0.2. MCP TypeScript SDK v2 splits client and server into two packages; v1 used the combined @modelcontextprotocol\u002Fsdk package.",[15,331,332,335,336,339],{},[174,333,334],{},"mcpToTool"," converts the discovered MCP tools into the interface Gemini uses. This is an integration specific to ",[174,337,338],{},"@google\u002Fgenai",", and it is still experimental in version 2.17.0; another provider's SDK may need a different adapter or tool-calling loop.",[193,341,344],{"className":342,"code":343,"language":198},[196],"import { GoogleGenAI, mcpToTool } from \"@google\u002Fgenai\";\n\nconst ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });\ntry {\n  const response = await ai.models.generateContent({\n    model: MODEL,\n    contents: USER_REQUEST,\n    config: {\n      tools: [mcpToTool(mcpClient)],\n      automaticFunctionCalling: { maximumRemoteCalls: MAX_TOOL_CALLS },\n    },\n  });\n  console.log(response.text);\n} finally {\n  await mcpClient.close();\n}\n",[174,345,343],{"__ignoreMap":24},[15,347,348],{},[236,349,352],{"href":350,"rel":351},"https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer\u002Fblob\u002Fmain\u002Fclient\u002Fclient.ts",[240],"Full source of the custom host\u002Fagent",[15,354,355,358],{},[74,356,357],{},"Claude Desktop and Claude Code are MCP Hosts that can register this server; other platforms may support MCP as well."," When switching model provider, the File MCP Server and the tool contract can stay unchanged, but the host\u002Fagent side usually has to change SDK, API key, adapter, and possibly the tool-calling loop.",[15,360,361],{},"To organize a real folder, run:",[193,363,366],{"className":364,"code":365,"language":198},[196],"npm run agent -- \"absolute_path\"\n",[174,367,365],{"__ignoreMap":24},[10,369,371],{"id":370},"running-the-demo-real-results-on-500-files","Running the demo: real results on 500 files",[15,373,374,375,377,378,380,381,384],{},"The run finished with 2 tool calls: one ",[174,376,190],{}," and one ",[174,379,205],{},". The results land under ",[174,382,383],{},"storage\u002Finbox\u002Forganized\u002F\u003Cextension>\u002F\u003Cyear>\u002F\u003Cmonth>\u002F\u003Cday>\u002F",". The images below are the transcript and the directory structure of that run.",[19,386],{"className":387,"alt":24,"src":388,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage9.png",[15,390,391],{},[30,392,394],{"className":393},[33],"The terminal at the start of the run: the npm run agent command and the first list_files call.",[19,396],{"className":397,"alt":24,"src":398,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage12.png",[15,400,401],{},[30,402,404],{"className":403},[33],"The terminal at the end: the last move_files call, 2 tool calls in total, and the completion message from Gemini.",[19,406],{"className":407,"alt":24,"src":408,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage2.png",[15,410,411],{},[30,412,414],{"className":413},[33],"The organized folder after the run, split across 7 extensions: xlsx, txt, png, pdf, jpg, docx, csv.",[19,416],{"className":417,"alt":24,"src":418,"style":26},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage8.png",[15,420,421],{},[30,422,424],{"className":423},[33],"One specific subfolder: organized\u002Fxlsx\u002F2026\u002F01\u002F29 holds exactly 1 file matching that modification date.",[15,426,427],{},"Much tidier, isn't it? To find something you only need to remember the extension and the modification date, and that is it.",[10,429,431],{"id":430},"registering-this-server-in-claude-desktop-or-claude-code","Registering this server in Claude Desktop or Claude Code",[15,433,434,436],{},[174,435,176],{}," is a host written by hand for the demo. Another way to use it is to register the built server file in Claude Desktop or Claude Code; the host then manages the connection, tools\u002Flist, and the tool-calling loop.",[15,438,439,440,443,444,304],{},"For Claude Desktop, open Settings → Developer → Edit Config and edit claude_desktop_config.json. A normal installation on Windows uses ",[174,441,442],{},"%APPDATA%\\Claude\\claude_desktop_config.json",". The Microsoft Store build may use the MSIX path at ",[174,445,446],{},"%LOCALAPPDATA%\\Packages\\\u003CPackageFamilyName>\\LocalCache\\Roaming\\Claude\\claude_desktop_config.json",[15,448,449],{},"Paths declared in this file must be absolute. If the file does not exist yet, create it:",[193,451,454],{"className":452,"code":453,"language":198},[196],"{\n  \"mcpServers\": {\n    \"file-organizer\": {\n      \"command\": \"node\",\n      \"args\": [\"absolute_path\u002Fdist\u002Fserver\u002Fserver.js\"],\n      \"env\": { \"ALLOWED_ROOTS\": \"C:\\\\Users\\\\you\\\\Downloads\" }\n    }\n  }\n}\n",[174,455,453],{"__ignoreMap":24},[15,457,458,460,461,304],{},[174,459,257],{}," has to contain exactly the folder you intend to organize: the example above only allows Downloads, so to try it on the demo's mock data, change it to the absolute path of ",[174,462,463],{},"storage\\inbox",[15,465,466,467,470,471,473,474,477],{},"For Claude Code, the project scope uses a ",[174,468,469],{},".mcp.json"," file at the project root. A relative path in ",[174,472,469],{}," is resolved against the working directory of the Claude Code process rather than the location of the file, so use an absolute path, or the ",[174,475,476],{},"${CLAUDE_PROJECT_DIR:-.}\u002Fdist\u002Fserver\u002Fserver.js"," variable that Claude Code sets for the server subprocess.",[15,479,480],{},"Result with Claude Desktop",[19,482],{"className":483,"alt":24,"src":484,"style":309},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage5.png",[15,486,487],{},[30,488,490],{"className":489},[33],"Claude Desktop: enabling the file-organizer connector under Connectors.",[15,492,493],{},"Result with Claude Code",[19,495],{"className":496,"alt":24,"src":497,"style":309},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage10.png",[15,499,500],{},[30,501,503],{"className":502},[33],"Claude Code: the file-organizer MCP server showing Connected through .mcp.json.",[15,505,506],{},"Reproducing it on the real Downloads folder:",[19,508],{"className":509,"alt":24,"src":510,"style":309},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage7.png",[15,512,513],{},[30,514,516],{"className":515},[33],"The real Downloads folder on the machine, used to try the demo outside the mock data.",[15,518,519],{},"Claude finds the tool it needs from the MCP server on its own:",[19,521],{"className":522,"alt":24,"src":523,"style":309},[22,23],"https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fimage1.png",[15,525,526],{},[30,527,529],{"className":528},[33],"Claude chooses to call the List files tool from file-organizer and asks for permission before reading the Downloads folder.",[10,531,533],{"id":532},"what-mcp-actually-standardizes","What MCP actually standardizes",[15,535,536],{},"The demo shows three standardized parts:",[52,538,539,542,545],{},[55,540,541],{},"Discovery: the client fetches tools and schemas at runtime through tools\u002Flist.",[55,543,544],{},"Invocation: the client calls every tool through tools\u002Fcall and receives results in the MCP structure.",[55,546,547,548,550,551,553,554,557,558,304],{},"Implementation boundary: the agent depends on ",[174,549,190],{},", ",[174,552,205],{},", not directly on ",[174,555,556],{},"fs.readdir"," or ",[174,559,560],{},"fs.rename",[15,562,563,564,550,566,568],{},"Architecturally — the backend behind ",[174,565,190],{},[174,567,205],{}," can change from the local file system to Google Drive, OneDrive, or S3 while the way the agent understands and calls the tools stays the same:",[193,570,573],{"className":571,"code":572,"language":198},[196],"                MCP Server\n                    │\n        ┌───────────┼───────────┐\n        ▼           ▼           ▼\n   Google Drive   OneDrive      S3\n",[174,574,572],{"__ignoreMap":24},[15,576,577],{},"MCP separates the agent's understanding of the tool contract from the way the server implements the tool.",[10,579,581],{"id":580},"when-mcp-is-worth-the-integration-cost","When MCP is worth the integration cost",[15,583,584],{},"MCP fits when a capability needs to be reused by several hosts, models, or development teams; when the tool list has to be discovered at runtime; or when the server needs to keep the boundary between permissions and business logic independent of the agent. Examples include looking up stock levels in an ERP, fetching shipment status, querying internal reports, and creating tickets.",[15,586,587],{},"If only one application calls one simple API, a direct adapter is usually less code. If the problem is fully determined, such as \"sort files by extension\", a plain script is still cheaper and more predictable than standing up a whole agent to decide for itself — even when that agent already uses a batch tool like move_files.",[10,589,591],{"id":590},"how-to-run-this-demo-yourself","How to run this demo yourself",[15,593,594],{},"The most direct way to experience the demo is to register the server in an existing MCP host — Claude Desktop or Claude Code — and then type the request in natural language in the chat box, exactly the way MCP is used in practice. The steps below apply on Windows with Node.js LTS.",[15,596,597,598,601,602,304],{},"1. Clone the repo and open PowerShell in the folder you just cloned: ",[174,599,600],{},"git clone https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer.git",", then ",[174,603,604],{},"cd mcp-file-organizer",[15,606,607,608,611],{},"2. Run ",[174,609,610],{},"npm ci"," to install the exact versions in the lockfile.",[15,613,614,615,601,618,621,622,304],{},"3. Run ",[174,616,617],{},"npm run test",[174,619,620],{},"npm run generate-mock"," to create 500 files in ",[174,623,624],{},"storage\u002Finbox",[15,626,627,628,631,632,304],{},"4. Run ",[174,629,630],{},"npm run build"," to produce ",[174,633,634],{},"dist\u002Fserver\u002Fserver.js",[15,636,637,638,640,641,304],{},"5. Register the built JavaScript file as described above, and set ",[174,639,257],{}," to exactly the folder you will organize — here, the absolute path to the repo's ",[174,642,463],{},[15,644,645],{},"6. Restart the host or open a new session, then ask it to organize that mock folder.",[15,647,648],{},"Example prompt:",[193,650,653],{"className":651,"code":652,"language":198},[196],"Organize all files in D:\\path\\to\\mcp-file-organizer\\storage\\inbox\n",[174,654,652],{"__ignoreMap":24},[15,656,657],{},"Check which permissions the host is applying before allowing the tool to write files. After the run, look at the results under storage\u002Finbox\u002Forganized.",[15,659,660],{},[236,661,664],{"href":662,"rel":663},"https:\u002F\u002Fgithub.com\u002Fbwv-labs\u002Fmcp-file-organizer",[240],"Source code",[10,666,668],{"id":667},"conclusion","Conclusion",[15,670,671],{},"MCP solves the problem stated at the start of the article: instead of every application–API pair needing its own adapter, one MCP Server can serve many compatible hosts, bringing the integration cost from roughly n × m closer to n + m when the components are genuinely reused. A fixed script is still simpler for a stable classification rule. The value of the demo is that the same MCP Server file served both a custom host\u002Fagent using Gemini and Claude Desktop, without changing the contract of list_files and move_files.",[10,673,675],{"id":674},"references","References",[52,677,678,685,692,699,706,713,720,727,734,741],{},[55,679,680],{},[236,681,684],{"href":682,"rel":683},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fintroduction",[240],"What is MCP?",[55,686,687],{},[236,688,691],{"href":689,"rel":690},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Flearn\u002Farchitecture",[240],"Architecture overview",[55,693,694],{},[236,695,698],{"href":696,"rel":697},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Fdevelop\u002Fbuild-server",[240],"Build an MCP server",[55,700,701],{},[236,702,705],{"href":703,"rel":704},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Fdevelop\u002Fbuild-client",[240],"Build an MCP client",[55,707,708],{},[236,709,712],{"href":710,"rel":711},"https:\u002F\u002Fmodelcontextprotocol.io\u002Fdocs\u002F2026-07-28\u002Fdevelop\u002Fconnect-local-servers",[240],"Connect to local MCP servers",[55,714,715],{},[236,716,719],{"href":717,"rel":718},"https:\u002F\u002Fcode.claude.com\u002Fdocs\u002Fen\u002Fmcp",[240],"Claude Code: connect to tools via MCP",[55,721,722],{},[236,723,726],{"href":724,"rel":725},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@google\u002Fgenai",[240],"google\u002Fgenai on npm (Google Gen AI SDK for TypeScript\u002FJavaScript)",[55,728,729],{},[236,730,733],{"href":731,"rel":732},"https:\u002F\u002Fgithub.com\u002Fmodelcontextprotocol\u002Ftypescript-sdk\u002Fblob\u002Fmain\u002Fdocs\u002Fmigration\u002Fupgrade-to-v2.md",[240],"MCP TypeScript SDK: upgrading from v1 to v2",[55,735,736],{},[236,737,740],{"href":738,"rel":739},"https:\u002F\u002Fai.google.dev\u002Fgemini-api\u002Fdocs\u002Fpricing",[240],"Gemini Developer API pricing",[55,742,743],{},[236,744,747],{"href":745,"rel":746},"https:\u002F\u002Fgoogleapis.github.io\u002Fjs-genai\u002Frelease_docs\u002Findex.html",[240],"@google\u002Fgenai API reference",{"title":24,"searchDepth":749,"depth":749,"links":750},2,[751,752,753,754,755,756,757,758,759,760,761,762,763,764],{"id":12,"depth":749,"text":13},{"id":43,"depth":749,"text":44},{"id":88,"depth":749,"text":89},{"id":128,"depth":749,"text":129},{"id":158,"depth":749,"text":159},{"id":183,"depth":749,"text":184},{"id":269,"depth":749,"text":270},{"id":370,"depth":749,"text":371},{"id":430,"depth":749,"text":431},{"id":532,"depth":749,"text":533},{"id":580,"depth":749,"text":581},{"id":590,"depth":749,"text":591},{"id":667,"depth":749,"text":668},{"id":674,"depth":749,"text":675},[766,767],"AI","tech talk","Thai Phan Quang","2026-09-07","Introduction. An LLM can answer questions, but it does not know by itself how to read a folder or call an internal API. Every capability needs an integration point to describe the tool, validate the input, call the real system and bring the result back into the conversation.","md",{},true,"\u002Fnews\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files",null,{"title":5,"description":770},"news\u002Fmcp-in-practice-building-an-ai-agent-that-organizes-files","https:\u002F\u002Fhomepage-media.s3.ap-southeast-1.amazonaws.com\u002Fwp-content\u002Fuploads\u002F2026\u002F09\u002F09151800\u002Fthumbnail.png","7sEcDf2N2aJC2EL8vs7egYuvl1WWmN-g5g0uvqFWXTc",1789638840413]