Koskamo

Staffing Math MCP

A community MCP server that calculates FTE requirements, monthly hiring plans, and Erlang C queue staffing needs for contact centers. It allows AI assistants to determine headcount based on workload, shrinkage, and occupancy, and generates hiring schedules to address volume growth and attrition.

Endpoint and access

No sign-in to connect and list tools. Individual tools may still need a vendor account, credentials, or permissions; check the documented prerequisites.

Staffing Math MCP logo, by Benjamin J. SunterCommunity listingStreamable HTTPNo sign-inEndpoint checked 2026-11-06Published by Benjamin J. Sunter
MCP endpoint (Streamable HTTP)
https://staffing-math.vercel.app/mcp
Vendor documentation

What its tools do

3 tools, grouped by the hints the server advertises. Hints are self-declared, not verified behavior.

3 of 3 have a description · 3 of 3 declare behavior hints · 3 of 3 with parameters document them all

Checks and signals

Each line is separate evidence, not a trust score.

  • Passed Endpoint answered the live check ·
  • Availability 1 of 1 checks passed in 30 days

What you can do with it

  • Calculate FTE needed for a specific workload volume
  • Generate a monthly hiring plan based on growth and attrition
  • Determine agent count for Erlang C queue service levels

Source and maintenance

Community listingRegistry 0.3.1
Maintenance checked

Protocol and capabilities

Server
staffing-math 0.3.1
MCP protocol negotiated in check
2025-11-25
Tools
3 advertised
Resources
1 advertised
Resource templates
0 advertised
Prompts
Not advertised
Server-advertised capabilities
{
  "tools": {
    "listChanged": true
  },
  "resources": {
    "listChanged": true
  }
}

Server-provided instructions

Untrusted server output shown for inspection. It is not Koskamo guidance and is excluded from knowledge search.

Staffing Math answers staffing questions with exact arithmetic from the Operating Model Simulator engine. Use these tools for any FTE, hiring or queue staffing number instead of calculating it yourself, and quote their numbers as returned. Every result lists its inputs as given or assumed. Tell the user which inputs were assumed and offer to rerun with their own numbers. Show the arithmetic steps when the user asks how a number was reached.

Tools

3 advertised

required_fte — FTE needed for a workload
Advertised by the server during the last successful anonymous check.
Advertised read-only

How many people a steady workload needs. Give weekly volume and minutes per case; shrinkage, target occupancy and hours per week fall back to stated defaults. Returns FTE, people to staff (rounded up), every input marked given or assumed, and the arithmetic with numbers filled in. Use hiring_plan when volume changes over time or the question is about hiring, and erlang_staffing for live queues with an answer-time target.

required_fte input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "weekly_volume",
    "minutes_per_case"
  ],
  "properties": {
    "shrinkage": {
      "type": "number",
      "maximum": 0.95,
      "minimum": 0,
      "description": "Share of paid time not available for the work: PTO, sick time, training, meetings. 0.30 means 30%. Default 0.3."
    },
    "weekly_volume": {
      "type": "number",
      "maximum": 1000000000,
      "description": "Cases, tickets or items arriving per week.",
      "exclusiveMinimum": 0
    },
    "minutes_per_case": {
      "type": "number",
      "maximum": 10000,
      "description": "Average handling time per case, in minutes.",
      "exclusiveMinimum": 0
    },
    "target_occupancy": {
      "type": "number",
      "maximum": 1,
      "minimum": 0.05,
      "description": "Share of available time you plan to keep people busy with the work. Default 0.85."
    },
    "hours_per_fte_week": {
      "type": "number",
      "maximum": 168,
      "description": "Paid hours per full-time person per week. Default 40.",
      "exclusiveMinimum": 0
    }
  }
}
required_fte output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "fte",
    "people",
    "weekly_work_hours",
    "productive_hours_per_fte_week",
    "planned_hours_per_fte_week",
    "fte_per_extra_minute",
    "answer",
    "notes",
    "inputs",
    "steps",
    "engine"
  ],
  "properties": {
    "fte": {
      "type": "number"
    },
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "steps": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "answer": {
      "type": "string"
    },
    "engine": {
      "type": "string"
    },
    "inputs": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "value",
          "shown",
          "source"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "shown": {
            "type": "string"
          },
          "value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            ]
          },
          "source": {
            "enum": [
              "given",
              "assumed"
            ],
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "people": {
      "type": "number"
    },
    "weekly_work_hours": {
      "type": "number"
    },
    "fte_per_extra_minute": {
      "type": "number"
    },
    "planned_hours_per_fte_week": {
      "type": "number"
    },
    "productive_hours_per_fte_week": {
      "type": "number"
    }
  },
  "additionalProperties": false
}
required_fte annotations
{
  "title": "FTE needed for a workload",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
hiring_plan — Monthly hiring plan
Advertised by the server during the last successful anonymous check.
Advertised read-only

Month-by-month hiring plan: how many requisitions to open each month so FTE on hand keeps up with FTE needed, after attrition, time to hire and ramp. Give weekly_volume with monthly_growth (0.05 means 5% a month, compounding), or weekly_volume_by_month. Unknown inputs fall back to stated defaults: current headcount defaults to what month 1 needs, attrition to 0, time to hire to 2 months, ramp to 0. When attrition is assumed, the result also gives the plan at 15% and 30%. Give hires_in_flight for people already hired or in the pipeline. Returns the plan table, totals, the arithmetic, and which inputs were assumed. A hire counts as zero capacity until fully ramped.

hiring_plan input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "minutes_per_case"
  ],
  "properties": {
    "months": {
      "type": "integer",
      "maximum": 36,
      "minimum": 1,
      "description": "Plan length in months when using monthly_growth. Default 12."
    },
    "shrinkage": {
      "type": "number",
      "maximum": 0.95,
      "minimum": 0,
      "description": "Share of paid time not available for the work: PTO, sick time, training, meetings. 0.30 means 30%. Default 0.3."
    },
    "ramp_months": {
      "type": "integer",
      "maximum": 24,
      "minimum": 0,
      "description": "Months from start date to full productivity. Default 0."
    },
    "start_month": {
      "type": "string",
      "description": "First month of the plan as YYYY-MM, used only to label months."
    },
    "weekly_volume": {
      "type": "number",
      "maximum": 1000000000,
      "description": "Weekly volume in month 1. Use with monthly_growth.",
      "exclusiveMinimum": 0
    },
    "monthly_growth": {
      "type": "number",
      "maximum": 1,
      "minimum": -0.5,
      "description": "Month-over-month change in weekly volume. 0.05 means 5% a month. Default 0."
    },
    "hires_in_flight": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "month",
          "count"
        ],
        "properties": {
          "count": {
            "type": "integer",
            "maximum": 100000,
            "minimum": 1,
            "description": "How many people."
          },
          "month": {
            "type": "integer",
            "maximum": 36,
            "minimum": 1,
            "description": "Plan month the people start counting as capacity. Use 1 for anyone counting from the start."
          }
        }
      },
      "maxItems": 36,
      "description": "People already hired or in the pipeline, and the month they start counting. These are the only way to cover months before a new requisition can land."
    },
    "annual_attrition": {
      "type": "number",
      "maximum": 0.99,
      "minimum": 0,
      "description": "Share of the team that leaves in a year. 0.25 means 25%. Default 0."
    },
    "minutes_per_case": {
      "type": "number",
      "maximum": 10000,
      "description": "Average handling time per case, in minutes.",
      "exclusiveMinimum": 0
    },
    "target_occupancy": {
      "type": "number",
      "maximum": 1,
      "minimum": 0.05,
      "description": "Share of available time you plan to keep people busy with the work. Default 0.85."
    },
    "current_headcount": {
      "type": "number",
      "maximum": 1000000,
      "minimum": 0,
      "description": "FTE on the team today. Default: what month 1 needs, rounded up."
    },
    "hours_per_fte_week": {
      "type": "number",
      "maximum": 168,
      "description": "Paid hours per full-time person per week. Default 40.",
      "exclusiveMinimum": 0
    },
    "time_to_hire_months": {
      "type": "integer",
      "maximum": 24,
      "minimum": 0,
      "description": "Months from opening a requisition to the start date. Default 2."
    },
    "weekly_volume_by_month": {
      "type": "array",
      "items": {
        "type": "number",
        "maximum": 1000000000,
        "exclusiveMinimum": 0
      },
      "maxItems": 36,
      "minItems": 1,
      "description": "Average weekly volume in each month of the plan, instead of weekly_volume and monthly_growth."
    }
  }
}
hiring_plan output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "total_reqs",
    "first_month_hires_can_count",
    "fte_needed_first_month",
    "fte_needed_last_month",
    "expected_leavers",
    "months_short_before_hires_land",
    "lookahead_months",
    "months_with_spare",
    "most_spare",
    "attrition_range",
    "months",
    "answer",
    "notes",
    "inputs",
    "steps",
    "engine"
  ],
  "properties": {
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "steps": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "answer": {
      "type": "string"
    },
    "engine": {
      "type": "string"
    },
    "inputs": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "value",
          "shown",
          "source"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "shown": {
            "type": "string"
          },
          "value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            ]
          },
          "source": {
            "enum": [
              "given",
              "assumed"
            ],
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "months": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "month",
          "label",
          "weekly_volume",
          "fte_needed",
          "reqs_to_open",
          "hires_counting",
          "expected_leavers",
          "fte_on_hand",
          "short_or_spare",
          "in_flight"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "month": {
            "type": "number"
          },
          "in_flight": {
            "type": "number"
          },
          "fte_needed": {
            "type": "number"
          },
          "fte_on_hand": {
            "type": "number"
          },
          "reqs_to_open": {
            "type": "number"
          },
          "weekly_volume": {
            "type": "number"
          },
          "hires_counting": {
            "type": "number"
          },
          "short_or_spare": {
            "type": "number"
          },
          "expected_leavers": {
            "type": "number"
          }
        },
        "additionalProperties": false
      }
    },
    "most_spare": {
      "anyOf": [
        {
          "type": "object",
          "required": [
            "month",
            "fte"
          ],
          "properties": {
            "fte": {
              "type": "number"
            },
            "month": {
              "type": "number"
            }
          },
          "additionalProperties": false
        },
        {
          "type": "null"
        }
      ]
    },
    "total_reqs": {
      "type": "number"
    },
    "attrition_range": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "annual_attrition",
          "total_reqs"
        ],
        "properties": {
          "total_reqs": {
            "type": "number"
          },
          "annual_attrition": {
            "type": "number"
          }
        },
        "additionalProperties": false
      }
    },
    "expected_leavers": {
      "type": "number"
    },
    "lookahead_months": {
      "type": "number"
    },
    "months_with_spare": {
      "type": "array",
      "items": {
        "type": "number"
      }
    },
    "fte_needed_last_month": {
      "type": "number"
    },
    "fte_needed_first_month": {
      "type": "number"
    },
    "first_month_hires_can_count": {
      "type": "number"
    },
    "months_short_before_hires_land": {
      "type": "array",
      "items": {
        "type": "number"
      }
    }
  },
  "additionalProperties": false
}
hiring_plan annotations
{
  "title": "Monthly hiring plan",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}
erlang_staffing — Queue staffing (Erlang C)
Advertised by the server during the last successful anonymous check.
Advertised read-only

Agents needed on a live queue (phone, chat) to answer a target share of contacts within a target time, using Erlang C. Give contacts per hour for the interval being staffed (usually the busiest hour) and handle time in seconds. Returns agents needed, the service level at that number and at one fewer, chance of waiting, occupancy, and people to schedule after shrinkage. Use required_fte for case or ticket work with no answer-time target.

erlang_staffing input schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "contacts_per_hour",
    "handle_time_seconds"
  ],
  "properties": {
    "shrinkage": {
      "type": "number",
      "maximum": 0.95,
      "minimum": 0,
      "description": "Share of paid time agents are off the queue. Converts agents to people scheduled. Default 0.3."
    },
    "service_level": {
      "type": "number",
      "maximum": 0.999,
      "minimum": 0.01,
      "description": "Share of contacts to answer within the target time. Default 0.8."
    },
    "contacts_per_hour": {
      "type": "number",
      "maximum": 100000,
      "description": "Contacts arriving per hour in the interval being staffed.",
      "exclusiveMinimum": 0
    },
    "handle_time_seconds": {
      "type": "number",
      "maximum": 36000,
      "description": "Average handle time in seconds: talk plus after-contact work.",
      "exclusiveMinimum": 0
    },
    "answer_within_seconds": {
      "type": "number",
      "maximum": 3600,
      "minimum": 0,
      "description": "Target answer time in seconds. Default 20."
    }
  }
}
erlang_staffing output schema
{
  "type": "object",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "agents",
    "service_level_at_agents",
    "service_level_one_fewer",
    "offered_load_erlangs",
    "probability_of_waiting",
    "occupancy",
    "people_to_schedule",
    "answer",
    "notes",
    "inputs",
    "steps",
    "engine"
  ],
  "properties": {
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "steps": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "agents": {
      "type": "number"
    },
    "answer": {
      "type": "string"
    },
    "engine": {
      "type": "string"
    },
    "inputs": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "value",
          "shown",
          "source"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "shown": {
            "type": "string"
          },
          "value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "number"
                }
              }
            ]
          },
          "source": {
            "enum": [
              "given",
              "assumed"
            ],
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    },
    "occupancy": {
      "type": "number"
    },
    "people_to_schedule": {
      "type": "number"
    },
    "offered_load_erlangs": {
      "type": "number"
    },
    "probability_of_waiting": {
      "type": "number"
    },
    "service_level_at_agents": {
      "type": "number"
    },
    "service_level_one_fewer": {
      "type": [
        "number",
        "null"
      ]
    }
  },
  "additionalProperties": false
}
erlang_staffing annotations
{
  "title": "Queue staffing (Erlang C)",
  "readOnlyHint": true,
  "openWorldHint": false,
  "idempotentHint": true,
  "destructiveHint": false
}

Prompts

Not advertised

No prompt definitions available.

Resources

1 advertised

Hiring plan view

Advertised by the server during the last successful anonymous check.

Chart of FTE needed against FTE on hand by month, with requisitions to open and controls that rerun the plan.

Hiring plan view URI
ui://staffing-math/hiring-plan.html

MIME type: text/html;profile=mcp-app

90-day uptime

A lightweight connection check about every six hours, sampled on hosts with many listings. It shows the server answered, not that its tools work.

Too few checks for a percentage yet (3 of 3 answered).

  1. 2026-11-07: 2 of 2 checks answered
  2. 2026-11-09: 1 of 1 checks answered

30-day availability

Aggregated from read-only remote checks; this is not a tool-execution or security guarantee.

Successful checks
1 of 1 (100%)
Average latency
645 ms
95th percentile latency
645 ms

Maintenance history

Public check and source-refresh events. Editorial and ownership records remain private.

  • · probe · passed · 645 ms · metadata changed · 3 tool(s), 1 resource(s), 0 resource template(s)

Connect from your client

Each snippet registers the same endpoint; pick the client you use.

This server is operated by the named third party. Listing and endpoint checks are not an endorsement or a security audit. Review the operator's permissions, terms, and data handling before connecting. How listings are checked and corrected.

Claude Code

Run this once in a terminal. It registers the remote server. Use --scope project to write .mcp.json for the repo.
Claude Code configuration
claude mcp add --transport http staffing-math-mcp https://staffing-math.vercel.app/mcp

Codex

Run this once. Codex stores MCP servers in ~/.codex/config.toml (or .codex/config.toml in a trusted project).
Codex configuration
codex mcp add staffing-math-mcp --url https://staffing-math.vercel.app/mcp

Cursor

Merge this server into .cursor/mcp.json in your project, then enable staffing-math-mcp in your MCP settings.
Cursor configuration
{
  "mcpServers": {
    "staffing-math-mcp": {
      "url": "https://staffing-math.vercel.app/mcp"
    }
  }
}

Cline

Merge this into cline_mcp_settings.json (Cline → MCP Servers → Configure).
Cline configuration
{
  "mcpServers": {
    "staffing-math-mcp": {
      "type": "streamableHttp",
      "url": "https://staffing-math.vercel.app/mcp",
      "disabled": false
    }
  }
}

VS Code

Merge this server into .vscode/mcp.json. Use MCP: List Servers to start staffing-math-mcp and inspect its tools.
VS Code configuration
{
  "servers": {
    "staffing-math-mcp": {
      "type": "http",
      "url": "https://staffing-math.vercel.app/mcp"
    }
  }
}

Gemini CLI

Run this once. It writes .gemini/settings.json in the current project; pass --scope user for ~/.gemini/settings.json.
Gemini CLI configuration
gemini mcp add --transport http staffing-math-mcp https://staffing-math.vercel.app/mcp

Claude Desktop

Open Claude Settings > Connectors > Add custom connector, enter this server URL and enable it for the conversation. This is a manual remote-connector handoff, not a desktop JSON configuration.
Claude Desktop configuration
https://staffing-math.vercel.app/mcp

Windsurf

Merge this into ~/.codeium/windsurf/mcp_config.json, then refresh MCP in Cascade. Windsurf remote servers use serverUrl, not url.
Windsurf configuration
{
  "mcpServers": {
    "staffing-math-mcp": {
      "serverUrl": "https://staffing-math.vercel.app/mcp"
    }
  }
}

Let your agent find its tools

Find servers like Staffing Math MCP from your agent. Add Koskamo once to search the index for endpoints, access requirements, and client setup. Public discovery needs no key.