Back to blog
    MCP Tools for AI Agency Client Workflows: Deliver Models as Tools, Not Files
    mcpagencyclient-deliverytoolsfine-tuningsegment:agency

    MCP Tools for AI Agency Client Workflows: Deliver Models as Tools, Not Files

    AI agencies typically deliver a model file. With MCP, you can deliver a Claude Desktop or Cursor tool that your client uses daily — recurring value that justifies a recurring retainer.

    EErtas Team·

    Most AI agencies deliver a model. They hand over a GGUF file, a deployment guide, an Ollama command. The client runs the model, gets value for a few weeks, then the model sits unused because integrating it into daily workflow requires effort the client did not budget for.

    MCP changes the delivery model. Instead of handing over a model file, you hand over a Claude Desktop or Cursor tool — a configured MCP server that your client installs in 5 minutes and uses every day as a natural extension of their existing AI workflow.

    The model runs on your infrastructure. You bill for access. The value is delivered daily. The retainer justifies itself.

    The Agency Delivery Shift

    Old delivery: Train model → export GGUF → write deployment guide → client figures out how to run it

    New delivery: Train model → deploy on your VPS → build MCP server → give client a config snippet → client adds it to Claude Desktop → done

    The client's setup is 4 lines of JSON and a restart of Claude Desktop. The rest is your infrastructure to maintain.

    Building Client-Ready MCP Tools

    Architecture:

    Client's Claude Desktop / Cursor
        ↓ MCP protocol
    Your MCP server (hosted on your VPS)
        ↓ HTTP
    Your Ollama instance (hosting client's fine-tuned model)
    

    The MCP server handles authentication (the client's API key), routing (which model to call based on client identity), and tool definitions specific to this client's use cases.

    Multi-client MCP server template:

    // agency-mcp-server.mjs
    import { Server } from '@modelcontextprotocol/sdk/server/index.js';
    import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
    import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
    
    // Client registry — maps API keys to model names and tool configs
    const CLIENT_CONFIG = {
      'client-a-key-xxxx': {
        modelName: 'real-estate-client-a-v3',
        tools: ['generate_listing', 'draft_followup', 'score_lead'],
        clientName: 'Sunrise Realty'
      },
      'client-b-key-yyyy': {
        modelName: 'ecommerce-client-b-v2',
        tools: ['product_description', 'support_response', 'classify_ticket'],
        clientName: 'BlueLine Commerce'
      }
    };
    
    const TOOL_DEFINITIONS = {
      generate_listing: {
        name: 'generate_listing',
        description: 'Generate a property listing description in our brokerage voice for Sunrise Realty.',
        inputSchema: {
          type: 'object',
          properties: {
            property_details: { type: 'string', description: 'Bedrooms, bathrooms, sqft, features, neighborhood' }
          },
          required: ['property_details']
        }
      },
      draft_followup: {
        name: 'draft_followup',
        description: 'Draft a personalized follow-up message to a real estate contact.',
        inputSchema: {
          type: 'object',
          properties: {
            contact_context: { type: 'string', description: 'Who this is, relationship, last interaction, any news' },
            goal: { type: 'string', description: 'What you want to accomplish with this message' }
          },
          required: ['contact_context']
        }
      },
      // ... other tools
    };
    
    // API key is passed via environment variable in Claude Desktop config
    const API_KEY = process.env.AGENCY_API_KEY;
    const client = CLIENT_CONFIG[API_KEY];
    
    if (!client) {
      process.stderr.write('Invalid API key\n');
      process.exit(1);
    }
    
    const server = new Server(
      { name: `agency-tools-${client.clientName}`, version: '1.0.0' },
      { capabilities: { tools: {} } }
    );
    
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: client.tools.map(toolName => TOOL_DEFINITIONS[toolName])
    }));
    
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
    
      if (!client.tools.includes(name)) {
        throw new Error(`Tool ${name} not available for your account`);
      }
    
      const prompt = buildPrompt(name, args);
    
      const response = await fetch('http://localhost:11434/api/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          model: client.modelName,
          messages: [{ role: 'user', content: prompt }],
          stream: false
        })
      });
    
      const data = await response.json();
      return { content: [{ type: 'text', text: data.message.content }] };
    });
    
    function buildPrompt(toolName, args) {
      const prompts = {
        generate_listing: `Write a listing description for:\n${args.property_details}`,
        draft_followup: `Draft a follow-up message.\nContext: ${args.contact_context}\nGoal: ${args.goal || 'Maintain relationship'}`,
        // ... other prompt builders
      };
      return prompts[toolName] || JSON.stringify(args);
    }
    
    const transport = new StdioServerTransport();
    await server.connect(transport);
    

    Client-Side Setup (What You Send the Client)

    // claude_desktop_config.json snippet — you provide this to client
    {
      "mcpServers": {
        "agency-tools": {
          "command": "node",
          "args": ["/path/to/agency-mcp-client.mjs"],
          "env": {
            "AGENCY_API_KEY": "client-a-key-xxxx",
            "AGENCY_SERVER_URL": "https://mcp.youragency.com"
          }
        }
      }
    }
    

    Or if you package this as an npm binary for even simpler setup:

    {
      "mcpServers": {
        "your-agency-name": {
          "command": "npx",
          "args": ["@youragency/mcp-client"],
          "env": {
            "API_KEY": "client-a-key-xxxx"
          }
        }
      }
    }
    

    Client runs npx @youragency/mcp-client once (or it auto-installs via npx), adds the config, restarts Claude Desktop. Done.

    The Retainer Justification

    Without MCP: Client has a GGUF file. They run it occasionally. The value is occasional. The retainer feels like paying for something they do not use daily.

    With MCP: Client uses your tool every time they open Claude Desktop. Every listing they generate, every follow-up they draft, every ticket they classify — your agency's tool is in the workflow. The value is daily and visible.

    Retainer tiers by tool access:

    • Basic ($400/month): 2 tools, quarterly retraining, email support
    • Standard ($700/month): 5 tools, monthly retraining, Slack support
    • Premium ($1,200/month): Unlimited tools, weekly model updates, priority support

    The "tools" frame is more intuitive for clients than the "model maintenance" frame. They are paying for access to tools they use every day.


    Ship AI that runs on your users' devices.

    Ertas early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.

    Further Reading

    Ship AI that runs on your users' devices.

    Early bird pricing starts at $14.50/mo — locked in for life. Plans for builders and agencies.

    Keep reading