Skip to main content
Glama
szlaskidaniel

subiekt123-mcp

subiekt123-mcp

Stateless MCP server for invoicing in Subiekt 123 (InsERT API).

  • SDK: @modelcontextprotocol/server 2.0.0, spec 2026-07-28

  • Transport: stateless Streamable HTTP (createMcpHandler) and stdio

  • Node ≥ 20, zod v4, ESM

The server keeps no state between requests - createMcpHandler builds a fresh McpServer instance per HTTP request, so it scales horizontally without session affinity and without Mcp-Session-Id.

New here? Start with GETTING-STARTED.md - credentials, first run, connecting a client, and issuing your first invoice. This README covers architecture and reference detail.

Safety gate: HMAC token

create_invoice does not accept an invoice payload - only a token issued by a preview_* tool.

preview_<template>_invoice({month:"2026-08"})
   → preview (buyer, line items, totals, rate, warnings)
   → token: <payload>.<HMAC-SHA256>     (valid for 10 minutes)

create_invoice({token})
   → signature + TTL verification
   → POST /documents

Why: in MCP it is the model that invokes a tool, and annotations.destructiveHint is only a hint to the client - the spec states plainly that annotations do not change how the SDK runs a tool. The token binds issuance to one specific, previously displayed document: changing even a single amount invalidates the signature. The token carries the whole payload, so the server stays stateless.

HMAC key: ~/.config/subiekt123/preview-hmac.key (32 random bytes, generated on first use, chmod 600). It lives outside credentials.json - a token leaked from a conversation transcript is not enough to forge approval.

Related MCP server: mcp-server-smallinvoice

Installation

cd subiekt123 && npm install

The repository contains no business data - no account numbers, no tax IDs, no amounts, no client names. All of that lives in two files under ~/.config/subiekt123/ (chmod 600).

1. Secrets - credentials.json

{
  "clientId": "<Client ID from the InsERT developer portal>",
  "clientSecret": "<Client Secret>",
  "redirectUri": "http://localhost:53682/callback",
  "subscriptionKey": "<Subiekt 123 API subscription key>",
  "subscriptionHeader": "Ocp-Apim-Subscription-Key"
}

Where to get them: konto.insert.com.plMoje produktyInsERT API → "Uzyskaj dostęp" → the Aplikacje menu → InsERT API (developers.insert.com.pl). There you will find Moje aplikacje (registration: scope subiekt123, return address = redirectUri) and Subskrypcje.

One-time OAuth consent:

npm run auth

The refreshToken rotates on every refresh and is persisted automatically. Inactivity for more than 90 days, or more than 365 days since the first consent, requires running npm run auth again.

2. Invoicing profile - profile.json

cp profile.example.json ~/.config/subiekt123/profile.json
chmod 600 ~/.config/subiekt123/profile.json

The profile describes the issuer, bank accounts, clients and invoice templates. Every template becomes an MCP tool named preview_<key>_invoice, so the tool set depends on the profile rather than on the code. The profile is validated at startup; a missing field or a reference to a non-existent client stops the server with a specific message.

Template field

Meaning

key

becomes part of the tool name: preview_<key>_invoice

kind

date rule: monthlyRecurring, sameDay, prepaid (see below)

title, summary

tool title and description as seen by the model

client, bankAccount

keys from the clients / bankAccounts sections

currency

PLN skips the rate; a foreign currency fetches the NBP rate automatically

dueDays

payment term in days, defaults to 14

defaultNetPrice

default amount; omit it to force the model to pass an amount explicitly

note, communityTransaction, jpkVatGroups

optional document fields

item

the single line item: name, sourceProductId, vatRate, pkwiu, gtu

Running

npm start          # stdio - the MCP client spawns the process itself
npm run start:http # stateless HTTP on 127.0.0.1:3123/mcp
npm run inspect    # MCP Inspector

Claude Code / Claude Desktop (stdio)

{
  "mcpServers": {
    "subiekt123": {
      "command": "node",
      "args": ["/path/to/subiekt123/src/stdio.mjs"]
    }
  }
}

HTTP

Bound to 127.0.0.1, with Host and Origin validation ahead of the handler (localhostHostValidation, localhostOriginValidation) - a defence against DNS rebinding, where a malicious site points its own domain at 127.0.0.1 so the browser treats the server as same-origin. A foreign Origin gets 403. PORT changes the port, HOST the address.

Tools

Tool

Annotations

Description

list_documents

read-only

Document list (0-indexed paging, pageSize ≤ 50)

get_document

read-only

Full document with line items

list_clients / get_client

read-only

Clients (get_client = 13 more fields)

list_products

read-only

Products

get_exchange_rate

read-only

NBP rate from the business day before the issue date

preview_<key>_invoice

read-only

One per profile template - dates and rate resolved automatically

preview_invoice

read-only

Arbitrary payload, outside the templates

create_invoice

destructive

Issues a document - from a token only

delete_document

destructive

Deletes a document (blocked once submitted to KSeF)

print_document

read-only

PDF as base64, optionally duplicate / ecoMode

Date rules (template.kind)

The rule lives in code (dates.mjs); a profile template merely selects it.

monthlyRecurring - recurring monthly invoice

Field

Rule

issueDate

today (overridable via the issueDate parameter)

deliveryDate, taxLiabilityDate

last day of the billed month (the month parameter)

dueDate

+dueDays from the issue date; weekend → Monday (Sat +2, Sun +1)

exchangeRate

NBP rate from the last business day before the issue date

Never backdate the issue date - KSeF reports an error when issueDate precedes the day of submission. Hence the default of today rather than the 1st of the month (user's decision, 2026-08-03). For the same reason a foreign-currency invoice cannot be issued ahead of time - the rate from the day before the issue date does not exist yet.

sameDay - one-off invoice

Issue date = sale date = the given date (defaults to today), due +dueDays with the weekend adjustment.

prepaid - invoice for a payment already received

Due date = issue date, and payments is filled automatically with the full amount. Always confirm the sale date with the user - it is the day the payment arrived, which may be earlier than the issue date. A sale date later than the issue date is rejected.

Public holidays are not shifted - weekends only (user's decision, 2026-07-31).

API pitfalls (confirmed empirically)

  1. calculationMethod must match the company's "Licz od" setting - otherwise the fiscalization status diverges. With Net pass netPrice, with Gross pass grossPrice.

  2. exchangeRate is mandatory for foreign currencies. The API does not fetch the rate itself (the UI does) - without it you get EXCHANGE_RATE_MUST_BE_SET_ON_DOCUMENT_IN_FOREIGN_CURRENCY.

  3. items[].symbol conflicts with sourceProductId - DOCUMENT_ITEM_SYMBOL_MUST_BE_EMPTY. The symbol comes from the product record.

  4. vatRate is a string: "23%" or a non-percentage rate such as "o.o." (reverse charge).

  5. eInvoice is a document type, not a KSeF submission. After POST the invoice has invoiceMode: "Ksef" but ksefNumber: null. Submission happens in the UI - the public API has no endpoint for it.

  6. A KSeF invoice cannot be printed before submission - DOCUMENT_MUST_BE_PROCESSED_BY_KSEF_BEFORE_PRINTING. The same error is returned for a non-existent id.

  7. An invoice for a natural person requires a PESEL (tinKind: "Pesel" + tin), otherwise it is created as a company. The UI does not require it - the discrepancy was reported to InsERT, with no response.

  8. pageNumber is 0-indexed, pageSize maxes out at 50 (60 → 400).

  9. POST /documents returns a nested {"id":{"value":"..."}}, without the document number.

  10. /printing requires Content-Type: application/json even with an empty body - otherwise 415. It returns a binary PDF.

  11. Numbers freed by deleted documents are reused.

  12. The public API does not list correction invoices (KFS) - they are visible only in the internal gw/api.

Layout

src/
├── server.mjs          McpServer factory - tool registration
├── stdio.mjs           stdio entry point
├── http.mjs            stateless HTTP entry point
├── auth.mjs            one-time OAuth PKCE consent
└── lib/
    ├── config.mjs      credentials.json
    ├── profile.mjs     profile.json - reading and validation
    ├── api.mjs         OAuth + HTTP client (JSON and binary)
    ├── token.mjs       HMAC preview → create
    ├── dates.mjs       date rules, weekend adjustment
    ├── nbp.mjs         NBP exchange rates
    ├── invoice.mjs     validation, totals, preview
    └── templates.mjs   assembling a payload from a profile template

profile.example.json    profile template (placeholders, no real data)
GETTING-STARTED.md      setup walkthrough, client wiring, troubleshooting
docs/api-notes.md       full API documentation gathered empirically
F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • Hosted MCP server for Mini Accountant: invoices, expenses, customers, analytics, tax estimates.

  • Brazilian fiscal MCP server - issue NF-e, NFC-e, NFS-e, CT-e, MDF-e and DC-e via SEFAZ.

  • MCP Server for agents to onboard, pay, and provision services autonomously with InFlow

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/szlaskidaniel/subiekt123-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server