Dutch Tax Income Calculator

A remote MCP server that calculates Dutch net/gross salary, compares scenarios, and looks up official tax brackets, so an AI assistant can answer real salary and tax questions instead of guessing. Every calculation is delegated to the open-source dutch-tax-income-calculator package — this server never reimplements tax logic or tax tables itself.

Indicative only — not tax advice. Confirm with a qualified Dutch tax advisor or the Belastingdienst before acting on any number this returns.

Server address — paste this into Claude or ChatGPT below:

https://thetax.nl/mcp

All three tools are read-only and require no authentication — nothing is written, changed, or deleted, and there's no sign-in step.

Example prompts, once connected:

Claude Claude.ai & Claude Desktop

  1. Open Settings → Connectors, click Add (top right), then Add custom connector.
    Claude Settings → Connectors page, with the Add menu open showing Browse connectors and Add custom connector
  2. Enter a name (e.g. "Dutch Tax Calculator") in the first field, and the server address from above in the second field. Leave Advanced settings (OAuth Client ID / Secret) empty — this server doesn't need them — then click Add.
    Claude Add custom connector dialog filled in with a name and the server URL
  3. Click Connect.
    Claude connector detail page showing a Connect button before the connector is connected
  4. In any chat, open the + menu and turn the connector on to start using it.

ChatGPT Web, Developer mode required

  1. Go to Settings → Security and login and turn on Developer mode (only needs to be done once). ChatGPT will flag it as elevated risk — that's expected, it's what gates custom/unverified connectors like this one.
    ChatGPT Settings → Security and login, with the Developer mode toggle turned on
  2. Go to Settings → Plugins.
    ChatGPT Settings → Plugins page, listing installed plugins and a Browse plugins entry
  3. Click Browse plugins, then the + button in the top right.
    ChatGPT Plugins gallery with a + button in the top right to add a new plugin
  4. Fill in the form: a Name (e.g. "Dutch Income Tax"), under Connection choose Server URL and paste the server address from above, set Authentication to No Auth, tick "I understand and want to continue", then click Create.
    ChatGPT New Plugin form filled in with name, server URL, and No Auth selected
  5. Click Connect in the confirmation popup.
    ChatGPT confirmation popup to add the plugin, with a Connect button
  6. It's now in your installed plugins — use it from the Plugins (@) menu in any chat.

Claude Code & Codex CLI Terminal

These are coding agents with shell/file access, so instead of clicking through settings, just hand them the command or prompt below and let them do it.

Claude Code

A real CLI command — run it directly in your terminal:

claude mcp add --transport http dutch-tax-income-calculator https://thetax.nl/mcp

Codex CLI

Codex's CLI only has a one-liner for local (stdio) servers; for a remote HTTP server like this one it needs a config.toml edit. Paste this into Codex chat and let it make the edit:

Add a remote MCP server to my Codex CLI config. Edit ~/.codex/config.toml (or ./.codex/config.toml for this project only) and add: [mcp_servers.dutch-tax-income-calculator] url = "https://thetax.nl/mcp" No authentication is needed, so no bearer_token_env_var or http_headers are required. Then confirm the entry was added correctly.

What it can do

Tax year data

Every tool takes a year parameter. Supported years are exactly the years present in the dutch-tax-income-calculator package's bundled data.json — nothing is extrapolated or assumed for years outside that set. Passing an unsupported year returns a clear error listing which years are currently supported, never a silent fallback to the nearest year. The full bracket data for a given year is also readable directly as the tax://brackets/{year} MCP resource.

Tools: input and output

calculate_net_salary

Gross → net for a given tax year. Input schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "year": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991,
      "description": "Dutch tax year, e.g. 2026. Must be a year present in the package's data.json — see the tax://brackets/{year} resource for supported years."
    },
    "allowance": {
      "default": false,
      "description": "Whether the income figure already includes the 8% holiday allowance (vakantiegeld)",
      "type": "boolean"
    },
    "socialSecurity": {
      "default": true,
      "description": "Whether Dutch social security contributions (AOW/Anw/Wlz) apply",
      "type": "boolean"
    },
    "older": {
      "default": false,
      "description": "Whether the person has reached state pension (AOW) age",
      "type": "boolean"
    },
    "hours": {
      "default": 40,
      "description": "Contracted hours per week",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "ruling": {
      "default": {
        "checked": false
      },
      "description": "30% ruling (30%-regeling) settings",
      "type": "object",
      "properties": {
        "checked": {
          "default": false,
          "description": "Whether the 30% ruling (30%-regeling) applies",
          "type": "boolean"
        },
        "choice": {
          "description": "Required when checked=true: 'normal' | 'young' (Master's degree, under 30) | 'research' (scientific research worker)",
          "type": "string",
          "enum": [
            "normal",
            "young",
            "research"
          ]
        }
      },
      "required": [
        "checked"
      ],
      "additionalProperties": false
    },
    "income": {
      "type": "number",
      "exclusiveMinimum": 0,
      "description": "Gross income, denominated in the unit given by startFrom"
    },
    "startFrom": {
      "default": "Year",
      "description": "Which unit `income` is expressed in",
      "type": "string",
      "enum": [
        "Year",
        "Month",
        "Week",
        "Day",
        "Hour"
      ]
    }
  },
  "required": [
    "year",
    "allowance",
    "socialSecurity",
    "older",
    "hours",
    "ruling",
    "income",
    "startFrom"
  ],
  "additionalProperties": false
}

Output shape (content[0].text, JSON-encoded):

{
  "normalizedInput": { /* the validated input, with defaults filled in */ },
  "result": { /* full computed paycheck: grossYear, netYear, netMonth, incomeTax, ... */ },
  "breakdown": [
    { "label": "Payroll tax (loonbelasting)", "amountYear": -2916, "amountMonth": -243 },
    { "label": "Social security contributions (volksverzekeringen)", "amountYear": -9954, "amountMonth": -829.5 },
    { "label": "General tax credit (algemene heffingskorting)", "amountYear": 2714.29, "amountMonth": 226.19 },
    { "label": "Labour tax credit (arbeidskorting)", "amountYear": 5498.04, "amountMonth": 458.17 }
  ],
  "assumptions": [ "This result is indicative only and is not tax advice ...", "..." ],
  "permalink": "https://thetax.nl/?year=2026&startFrom=Year&salary=36000&..."
}

calculate_gross_from_net

Net → gross. Input schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "year": {
      "type": "integer",
      "minimum": -9007199254740991,
      "maximum": 9007199254740991,
      "description": "Dutch tax year, e.g. 2026. Must be a year present in the package's data.json — see the tax://brackets/{year} resource for supported years."
    },
    "allowance": {
      "default": false,
      "description": "Whether the income figure already includes the 8% holiday allowance (vakantiegeld)",
      "type": "boolean"
    },
    "socialSecurity": {
      "default": true,
      "description": "Whether Dutch social security contributions (AOW/Anw/Wlz) apply",
      "type": "boolean"
    },
    "older": {
      "default": false,
      "description": "Whether the person has reached state pension (AOW) age",
      "type": "boolean"
    },
    "hours": {
      "default": 40,
      "description": "Contracted hours per week",
      "type": "number",
      "exclusiveMinimum": 0
    },
    "ruling": {
      "default": {
        "checked": false
      },
      "description": "30% ruling (30%-regeling) settings",
      "type": "object",
      "properties": {
        "checked": {
          "default": false,
          "description": "Whether the 30% ruling (30%-regeling) applies",
          "type": "boolean"
        },
        "choice": {
          "description": "Required when checked=true: 'normal' | 'young' (Master's degree, under 30) | 'research' (scientific research worker)",
          "type": "string",
          "enum": [
            "normal",
            "young",
            "research"
          ]
        }
      },
      "required": [
        "checked"
      ],
      "additionalProperties": false
    },
    "targetNet": {
      "type": "number",
      "exclusiveMinimum": 0,
      "description": "Target net income to solve for"
    },
    "field": {
      "default": "netYear",
      "description": "Which net figure targetNet refers to: netYear (annual) or netMonth (monthly)",
      "type": "string",
      "enum": [
        "netYear",
        "netMonth"
      ]
    },
    "holidayAllowanceIncluded": {
      "default": false,
      "description": "Whether targetNet already includes the net holiday allowance payout",
      "type": "boolean"
    }
  },
  "required": [
    "year",
    "allowance",
    "socialSecurity",
    "older",
    "hours",
    "ruling",
    "targetNet",
    "field",
    "holidayAllowanceIncluded"
  ],
  "additionalProperties": false
}

Output shape is the same as calculate_net_salary, except when rounding makes the gross non-unique or no gross produces the target net — see below.

{
  "normalizedInput": { /* ... */ },
  "result": { "plateau": true, "grossLow": 35999.5, "grossHigh": 36000.49 },
  "breakdown": [ /* computed at grossHigh */ ],
  "assumptions": [ "...", "Rounding to 2 decimals means more than one gross value produces this net income; ..." ],
  "permalink": "https://thetax.nl/?..."
}

If no gross produces the target net at all, the tool returns isError: true with the package's own error message verbatim (e.g. naming the nearest achievable net) — never a generic error.

compare_scenarios

2–5 scenarios, each shaped like calculate_net_salary's input plus an optional label. Input schema:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "scenarios": {
      "minItems": 2,
      "maxItems": 5,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "year": {
            "type": "integer",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Dutch tax year, e.g. 2026. Must be a year present in the package's data.json — see the tax://brackets/{year} resource for supported years."
          },
          "allowance": {
            "default": false,
            "description": "Whether the income figure already includes the 8% holiday allowance (vakantiegeld)",
            "type": "boolean"
          },
          "socialSecurity": {
            "default": true,
            "description": "Whether Dutch social security contributions (AOW/Anw/Wlz) apply",
            "type": "boolean"
          },
          "older": {
            "default": false,
            "description": "Whether the person has reached state pension (AOW) age",
            "type": "boolean"
          },
          "hours": {
            "default": 40,
            "description": "Contracted hours per week",
            "type": "number",
            "exclusiveMinimum": 0
          },
          "ruling": {
            "default": {
              "checked": false
            },
            "description": "30% ruling (30%-regeling) settings",
            "type": "object",
            "properties": {
              "checked": {
                "default": false,
                "description": "Whether the 30% ruling (30%-regeling) applies",
                "type": "boolean"
              },
              "choice": {
                "description": "Required when checked=true: 'normal' | 'young' (Master's degree, under 30) | 'research' (scientific research worker)",
                "type": "string",
                "enum": [
                  "normal",
                  "young",
                  "research"
                ]
              }
            },
            "required": [
              "checked"
            ],
            "additionalProperties": false
          },
          "income": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Gross income, denominated in the unit given by startFrom"
          },
          "startFrom": {
            "default": "Year",
            "description": "Which unit `income` is expressed in",
            "type": "string",
            "enum": [
              "Year",
              "Month",
              "Week",
              "Day",
              "Hour"
            ]
          },
          "label": {
            "description": "Optional label to identify this scenario in the comparison table",
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          }
        },
        "required": [
          "year",
          "allowance",
          "socialSecurity",
          "older",
          "hours",
          "ruling",
          "income",
          "startFrom"
        ],
        "additionalProperties": false
      },
      "description": "Between 2 and 5 salary scenarios to compare, each shaped like calculate_net_salary's input"
    }
  },
  "required": [
    "scenarios"
  ],
  "additionalProperties": false
}
{
  "assumptions": [ "This result is indicative only and is not tax advice ..." ],
  "scenarios": [
    { "index": 1, "label": "Current", "normalizedInput": { /* ... */ }, "result": { /* ... */ }, "breakdown": [ /* ... */ ], "assumptions": [ /* ... */ ], "permalink": "..." },
    { "index": 2, "label": "After raise", "...": "..." }
  ],
  "comparisonTable": "| # | Label | Year | Gross/yr | Net/yr | Net/mo | Income tax | Effective tax rate |\n|---|---|---|---|---|---|---|---|\n| 1 | Current | 2026 | ... |"
}
Manual client config (VS Code, Cursor, raw MCP clients)

claude_desktop_config.json (manual config via the mcp-remote bridge):

{
  "mcpServers": {
    "dutch-tax-income-calculator": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://thetax.nl/mcp",
        "--transport",
        "http-only"
      ]
    }
  }
}

mcp.json (VS Code, Cursor, and other clients supporting the standard config format):

{
  "servers": {
    "dutch-tax-income-calculator": {
      "type": "http",
      "url": "https://thetax.nl/mcp"
    }
  }
}

Rate limits and privacy

60 requests/minute per client, no authentication. No request bodies, tool arguments, or calculated amounts are ever logged — see the Privacy Policy for details.

Support

Source and issue tracker: https://github.com/stevermeister/dutch-tax-income-calculator-mcp. Contact: stevermeister@gmail.com.