Close Menu
AI News TodayAI News Today

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    What's Hot

    NOAA is putting commercial fishing ahead of conservation

    Group of bipartisan lawmakers ask US government to ban several hack-for-hire firms

    When One Process Becomes Too Much: Splitting a Pipeline into MCP Services

    Facebook X (Twitter) Instagram
    • About Us
    • Contact Us
    Facebook X (Twitter) Instagram Pinterest Vimeo
    AI News TodayAI News Today
    • Home
    • AI News
    • AI Reviews
    • AI Tools
    • AI Tutorials
    • Chatbots
    • Free AI Tools
    • Artificial Intelligence
    AI News TodayAI News Today
    Home»AI Tools»When One Process Becomes Too Much: Splitting a Pipeline into MCP Services
    AI Tools

    When One Process Becomes Too Much: Splitting a Pipeline into MCP Services

    By No Comments9 Mins Read
    Share Facebook Twitter Pinterest LinkedIn Tumblr Reddit Telegram Email
    When One Process Becomes Too Much: Splitting a Pipeline into MCP Services
    Share
    Facebook Twitter LinkedIn Pinterest Email

    The usual way to build a multi-part pipeline is to put every part in the same process and call between them with function calls. It’s the path of least resistance, and for a while it’s genuinely fine until the parts stop being small enough to treat as implementation detail. Once each one is really a mini-application in its own right, with its own dependencies, its own failure modes, and its own release cadence, sharing a process with the others stops being convenient and starts being the thing that breaks first. That’s the point we’d reached, and it’s why we put a real boundary, an MCP server around each one instead.

    Table of contents

    1. The usual approach and where it stops working
    2. Why the boundary matters more than the protocol
    3. Two servers deliberately sharing nothing
    4. Calling a tool with nothing but the SDK
    5. Wiring services into an orchestrator
    6. What this doesn’t solve yet

    ···

    The usual approach and where it stops working

    Here’s what we had before one orchestrator class, each service instantiated inside it, everything sharing a process and a virtual environment:

    # the old way: illustrative, not real code, but the shape is realclass Orchestrator:    def __init__(self):        self.extraction = ExtractionEngine()   # its own heavy dependency tree        self.risk = RiskEngine()               # a different version of the same library        self.fraud = FraudEngine()             # imports something that conflicts with both    def run(self, document):        fields = self.extraction.extract(document)   # unhandled exception here        score = self.risk.score(fields)               # this line never runs        return score

    Three separate failure modes hide in those seven lines, and none of them are exotic. An unhandled exception in extraction takes risk scoring down with it, because there’s no boundary between them, just a call stack and the pipeline doesn’t degrade, it stops. A dependency bump for the fraud engine can break the extraction engine’s install, because pip doesn’t know or care that these are conceptually separate services; it only knows they share one environment. And shipping a bug fix to any single service means redeploying the whole application, because there’s only one application to deploy, one process to restart, one blast radius that includes everything.

    The issues show up months after release, not in the demo, once each engine has grown enough real logic and enough real dependencies that “just import it” stops being free. The failure is usually a version pin that quietly forces a downgrade somewhere else, or a memory-heavy extraction run that starves the risk engine of resources it needs in the same process, or a one-line change to fraud detection that nobody thought to test against extraction because on paper they’re unrelated. They share an address space, which is a much tighter coupling than anyone designing them separately intended.

    ···

    Why the boundary matters more than the protocol

    The actual fix isn’t MCP specifically, it’s a real process boundary around each service so a crash in one doesn’t touch the others, a dependency change in one doesn’t ripple into another, and each can be deployed, scaled, and restarted on its own schedule. You could get that boundary with plain REST endpoints and hand-rolled clients, and plenty of systems do exactly that, successfully. What MCP adds on top is a single, consistent way for any orchestrator, any agent, any future consumer, to discover what a service can do and call it, without writing a bespoke integration per consumer per service.

    That distinction is worth holding onto, because it stops you reaching for MCP out of momentum rather than justification. If a service only ever has one caller and that’s never going to change, a direct internal API is simpler and a protocol layer buys you nothing. The case for MCP here is specific: an orchestrator that needs a uniform way to reach a growing number of services, calling different ones under different conditions. That’s a real reason to use it.

    ···

    Two servers deliberately sharing nothing

    Writing an MCP tool isn’t the interesting part; decorate a function, type-hint the arguments, and FastMCP handles the schema. If that mechanic is new to you, it’s the same three lines regardless of what the tool does. The interesting part, and the part that’s easy to get wrong even once you know the syntax, is what you deliberately leave out of each server so the isolation is real instead of cosmetic.

    Here’s the extraction service:

    # extraction_server.pyfrom fastmcp import FastMCPmcp = FastMCP("extraction-tools")class ExtractionFailedError(Exception):    pass@mcp.tooldef extract_fields(document: str) -> dict:    """Extract structured fields from a raw document."""    result = extraction_engine.run(document)    if result.confidence < 0.6:        raise ExtractionFailedError(            f"Extraction confidence {result.confidence:.2f} below threshold"        )    return result.fieldsif __name__ == "__main__":    mcp.run(        transport="http",        host="0.0.0.0",        port=8931,    )

    And risk scoring:

    # risk_server.pyfrom fastmcp import FastMCPmcp = FastMCP("risk-tools")@mcp.tooldef score_risk(fields: dict) -> dict:    """Score extracted fields for risk."""    return risk_engine.score(fields)if __name__ == "__main__":    mcp.run(        transport="http",        host="0.0.0.0",        port=8932,    )

    Neither file imports the other, neither has the other’s dependencies installed anywhere near it. They run as two separate processes, on two separate ports, and that’s not an incidental deployment detail, it’s the entire reason this exists. If the risk engine needs a library upgrade next quarter, that’s a change to one process, tested and shipped on its own schedule, and extraction_server.py never finds out it happened. Streamable HTTP is the transport that makes sense for this deployment model; stdio ties the server’s lifetime to the process that spawned it, which works well for local clients but not for independently deployed services.

    What’s easy to miss is that the boundary only holds if you actually respect it while writing the tools. It’s tempting, once both servers exist, to have extraction_server.py import a shared utils.py that also happens to be imported by risk_server.py. Its convenient, but it quietly recreates the exact coupling you built two servers to avoid. If both need the same validation logic, that logic either gets duplicated deliberately in each service, or it becomes its own third service with its own tool. The isolation is a discipline, not a default you get for free by using MCP.

    ···

    Calling a tool with nothing but the SDK

    Before any orchestration framework enters the picture, it’s worth seeing what a client actually does, because everything built on top of this is calling the same two methods:

    from mcp import ClientSessionfrom mcp.client.streamable_http import streamablehttp_clientasync def call_extraction(raw_text: str):    async with streamablehttp_client(        "http://localhost:8931/mcp"    ) as (read, write, _):        async with ClientSession(read, write) as session:            await session.initialize()            tools = await session.list_tools()            print([tool.name for tool in tools.tools])            result = await session.call_tool(                "extract_fields",                {"document": raw_text},            )            return result

    Discover what’s available, call one of those things by name with arguments. Everything else like schemas, transports, multi-server routing exists to make that pattern work at scale, not to replace it with something more complicated.

    ···

    Wiring services into an orchestrator

    The orchestrator’s job isn’t picking one tool and calling it, it’s computing a path through services, where each step’s output becomes the next step’s input. langchain-mcp-adapters connects to multiple servers at once, and LangGraph’s StateGraph is a reasonable way to express that path explicitly:

    from typing import TypedDictfrom langchain_mcp_adapters.client import MultiServerMCPClientfrom langgraph.graph import END, START, StateGraphclient = MultiServerMCPClient(    {        "extraction": {            "url": "http://localhost:8931/mcp",            "transport": "streamable_http"        },        "risk": {            "url": "http://localhost:8932/mcp",            "transport": "streamable_http"        }    })class PipelineState(TypedDict):    document: str    fields: dict    risk_score: dictasync def extraction_node(state: PipelineState):    tools = await client.get_tools(server_name="extraction")    tool = next(t for t in tools if t.name == "extract_fields")    fields = await tool.ainvoke({"document": state["document"]})    return {"fields": fields}async def risk_node(state: PipelineState):    tools = await client.get_tools(server_name="risk")    tool = next(t for t in tools if t.name == "score_risk")    score = await tool.ainvoke({"fields": state["fields"]})    return {"risk_score": score}graph = StateGraph(PipelineState)graph.add_node("extract", extraction_node)graph.add_node("score_risk", risk_node)graph.add_edge(START, "extract")graph.add_edge("extract", "score_risk")graph.add_edge("score_risk", END)app = graph.compile()

    This is a fixed path: extraction always feeds risk scoring, no decision involved. That’s honest for a two-service pipeline, and it’s worth building this plainly before reaching for anything smarter. The moment a third or fourth service joins and the path genuinely depends on what a given document contains, add_edge stops being enough and you need add_conditional_edges, where the orchestrator decides which service to call next based on what the previous one returned.

    ···

    What this doesn’t solve yet

    This gets you isolation and a clean way to route between services through an orchestrator that decides the order. It says nothing about what happens when that order turns out to be wrong mid-execution when a service three steps into the path discovers it’s missing a field that only an earlier service could have supplied, and the orchestrator has to go back rather than forward. A fixed edge from extraction to risk scoring has no way to express that. It also says nothing about what a service failure actually looks like once it’s a network call away instead of a Python exception three stack frames up, or what happens once the orchestrator is choosing between more services than a single decision can hold clearly. Those are separate, real problems, and they stay separate rather than getting squeesed into the end of this one.

    The problem I actually opened with is gone after using MCP. Extraction and risk scoring haven’t shared a stack trace since, a dependency bump in one has never once touched the other, and shipping a fix to either no longer means redeploying both. That was true before I’d solved a single one of the harder questions above, which tells you something about how much of the original pain was really about isolation and how little of it was ever about the protocol.

    This is the first piece to come out of rebuilding this the right way, and it won’t be the last. There’s enough sitting unresolved in what I’ve just listed; the path that turns out wrong mid-execution, the failure that looks different over a network than it did in a stack trace, the point where an orchestrator has more services to choose from than one decision can hold cleanly, that a single article was never going to close all of it out, and I’d rather leave it open than force a tidier ending than the system actually has right now.

    MCP Pipeline process services splitting
    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email
    Previous ArticleAmazon Prime Video’s new AI tech matches lips to dubbed audio
    Next Article Group of bipartisan lawmakers ask US government to ban several hack-for-hire firms
    • Website

    Related Posts

    AI Tools

    Free AI Generator: What Real Free Tools Can Do (and What They Can’t)

    AI Tools

    10 Statistical Traps We Often Overlook

    AI Tools

    Free AI Chat in 2025: What You Actually Get for Nothing

    Add A Comment
    Leave A Reply Cancel Reply

    Top Posts

    NOAA is putting commercial fishing ahead of conservation

    0 Views

    Group of bipartisan lawmakers ask US government to ban several hack-for-hire firms

    0 Views

    When One Process Becomes Too Much: Splitting a Pipeline into MCP Services

    0 Views
    Stay In Touch
    • Facebook
    • YouTube
    • TikTok
    • WhatsApp
    • Twitter
    • Instagram
    Latest Reviews
    AI Tutorials

    Quantization from the ground up

    AI Tools

    David Sacks is done as AI czar — here’s what he’s doing instead

    AI Reviews

    Judge sides with Anthropic to temporarily block the Pentagon’s ban

    Subscribe to Updates

    Get the latest tech news from FooBar about tech, design and biz.

    Most Popular

    NOAA is putting commercial fishing ahead of conservation

    0 Views

    Group of bipartisan lawmakers ask US government to ban several hack-for-hire firms

    0 Views

    When One Process Becomes Too Much: Splitting a Pipeline into MCP Services

    0 Views
    Our Picks

    Quantization from the ground up

    David Sacks is done as AI czar — here’s what he’s doing instead

    Judge sides with Anthropic to temporarily block the Pentagon’s ban

    Subscribe to Updates

    Get the latest creative news from FooBar about art, design and business.

    Facebook X (Twitter) Instagram Pinterest
    • About Us
    • Contact Us
    • Terms & Conditions
    • Privacy Policy
    • Disclaimer

    © 2026 ainewstoday.co. All rights reserved. Designed by DD.

    Type above and press Enter to search. Press Esc to cancel.