rechnungsapi-mcp 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,6 +75,7 @@ See [`deploy/nginx-mcp.conf`](deploy/nginx-mcp.conf) for a ready-to-use reverse
75
75
  {
76
76
  "mcpServers": {
77
77
  "rechnungsapi": {
78
+ "type": "http",
78
79
  "url": "https://mcp.rechnungsapi.de/mcp",
79
80
  "headers": { "Authorization": "Bearer <their-own-rechnungsapi-token>" }
80
81
  }
package/dist/http.js CHANGED
@@ -44,7 +44,7 @@ async function handleMcpRequest(req, res) {
44
44
  mcpServer.close();
45
45
  });
46
46
  await mcpServer.connect(transport);
47
- const body = req.method === "POST" ? await readJsonBody(req) : void 0;
47
+ const body = await readJsonBody(req);
48
48
  await transport.handleRequest(req, res, body);
49
49
  }
50
50
  var server = http.createServer(async (req, res) => {
@@ -56,6 +56,10 @@ var server = http.createServer(async (req, res) => {
56
56
  sendJson(res, 404, { error: "Not found. POST to /mcp." });
57
57
  return;
58
58
  }
59
+ if (req.method !== "POST") {
60
+ sendJson(res, 405, { error: "Only POST is supported on /mcp (no server-initiated SSE stream)." });
61
+ return;
62
+ }
59
63
  try {
60
64
  await handleMcpRequest(req, res);
61
65
  } catch (error) {
package/dist/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/http.ts"],"sourcesContent":["import http from \"node:http\"\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\"\nimport { RechnungsApiClient } from \"rechnungsapi-sdk\"\nimport { registerTools } from \"./tools.js\"\n\nconst PORT = Number(process.env.PORT ?? 3939)\n\nfunction extractBearerToken(req: http.IncomingMessage): string | null {\n const header = req.headers[\"authorization\"]\n if (!header || Array.isArray(header)) return null\n const match = /^Bearer\\s+(.+)$/i.exec(header)\n return match ? match[1] : null\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk as Buffer)\n const raw = Buffer.concat(chunks).toString(\"utf8\")\n return raw ? JSON.parse(raw) : undefined\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown) {\n res.writeHead(status, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify(body))\n}\n\n/**\n * One request = one throwaway MCP server + SDK client, built from that\n * request's own Bearer token. Stateless mode (sessionIdGenerator: undefined)\n * is what makes this safe for many different customers hitting the same\n * running process concurrently — nothing is shared or cached between them.\n */\nasync function handleMcpRequest(req: http.IncomingMessage, res: http.ServerResponse) {\n const token = extractBearerToken(req)\n if (!token) {\n sendJson(res, 401, { error: \"Missing Authorization: Bearer <your-rechnungsapi-token> header\" })\n return\n }\n\n const client = new RechnungsApiClient({\n apiToken: token,\n baseUrl: process.env.RECHNUNGSAPI_BASE_URL,\n v2BaseUrl: process.env.RECHNUNGSAPI_V2_BASE_URL,\n })\n\n const mcpServer = new McpServer({ name: \"rechnungsapi-mcp\", version: \"0.1.0\" })\n registerTools(mcpServer, client)\n\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })\n res.on(\"close\", () => {\n transport.close()\n mcpServer.close()\n })\n\n await mcpServer.connect(transport)\n const body = req.method === \"POST\" ? await readJsonBody(req) : undefined\n await transport.handleRequest(req, res, body)\n}\n\nconst server = http.createServer(async (req, res) => {\n if (req.method === \"GET\" && req.url === \"/health\") {\n sendJson(res, 200, { ok: true })\n return\n }\n\n if (req.url !== \"/mcp\") {\n sendJson(res, 404, { error: \"Not found. POST to /mcp.\" })\n return\n }\n\n try {\n await handleMcpRequest(req, res)\n } catch (error) {\n console.error(\"Request failed:\", error)\n if (!res.headersSent) {\n sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) })\n }\n }\n})\n\nserver.listen(PORT, () => {\n console.log(`rechnungsapi-mcp HTTP server listening on port ${PORT}`)\n})\n"],"mappings":";;;;;;AAAA,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,qCAAqC;AAC9C,SAAS,0BAA0B;AAGnC,IAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAE5C,SAAS,mBAAmB,KAA0C;AACpE,QAAM,SAAS,IAAI,QAAQ,eAAe;AAC1C,MAAI,CAAC,UAAU,MAAM,QAAQ,MAAM,EAAG,QAAO;AAC7C,QAAM,QAAQ,mBAAmB,KAAK,MAAM;AAC5C,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAEA,eAAe,aAAa,KAA6C;AACvE,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,IAAK,QAAO,KAAK,KAAe;AAC1D,QAAM,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AACjD,SAAO,MAAM,KAAK,MAAM,GAAG,IAAI;AACjC;AAEA,SAAS,SAAS,KAA0B,QAAgB,MAAe;AACzE,MAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAC5D,MAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAQA,eAAe,iBAAiB,KAA2B,KAA0B;AACnF,QAAM,QAAQ,mBAAmB,GAAG;AACpC,MAAI,CAAC,OAAO;AACV,aAAS,KAAK,KAAK,EAAE,OAAO,iEAAiE,CAAC;AAC9F;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,mBAAmB;AAAA,IACpC,UAAU;AAAA,IACV,SAAS,QAAQ,IAAI;AAAA,IACrB,WAAW,QAAQ,IAAI;AAAA,EACzB,CAAC;AAED,QAAM,YAAY,IAAI,UAAU,EAAE,MAAM,oBAAoB,SAAS,QAAQ,CAAC;AAC9E,gBAAc,WAAW,MAAM;AAE/B,QAAM,YAAY,IAAI,8BAA8B,EAAE,oBAAoB,OAAU,CAAC;AACrF,MAAI,GAAG,SAAS,MAAM;AACpB,cAAU,MAAM;AAChB,cAAU,MAAM;AAAA,EAClB,CAAC;AAED,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,OAAO,IAAI,WAAW,SAAS,MAAM,aAAa,GAAG,IAAI;AAC/D,QAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAC9C;AAEA,IAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,MAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,WAAW;AACjD,aAAS,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAC/B;AAAA,EACF;AAEA,MAAI,IAAI,QAAQ,QAAQ;AACtB,aAAS,KAAK,KAAK,EAAE,OAAO,2BAA2B,CAAC;AACxD;AAAA,EACF;AAEA,MAAI;AACF,UAAM,iBAAiB,KAAK,GAAG;AAAA,EACjC,SAAS,OAAO;AACd,YAAQ,MAAM,mBAAmB,KAAK;AACtC,QAAI,CAAC,IAAI,aAAa;AACpB,eAAS,KAAK,KAAK,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IACtF;AAAA,EACF;AACF,CAAC;AAED,OAAO,OAAO,MAAM,MAAM;AACxB,UAAQ,IAAI,kDAAkD,IAAI,EAAE;AACtE,CAAC;","names":[]}
1
+ {"version":3,"sources":["../src/http.ts"],"sourcesContent":["import http from \"node:http\"\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\"\nimport { StreamableHTTPServerTransport } from \"@modelcontextprotocol/sdk/server/streamableHttp.js\"\nimport { RechnungsApiClient } from \"rechnungsapi-sdk\"\nimport { registerTools } from \"./tools.js\"\n\nconst PORT = Number(process.env.PORT ?? 3939)\n\nfunction extractBearerToken(req: http.IncomingMessage): string | null {\n const header = req.headers[\"authorization\"]\n if (!header || Array.isArray(header)) return null\n const match = /^Bearer\\s+(.+)$/i.exec(header)\n return match ? match[1] : null\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk as Buffer)\n const raw = Buffer.concat(chunks).toString(\"utf8\")\n return raw ? JSON.parse(raw) : undefined\n}\n\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown) {\n res.writeHead(status, { \"content-type\": \"application/json\" })\n res.end(JSON.stringify(body))\n}\n\n/**\n * One request = one throwaway MCP server + SDK client, built from that\n * request's own Bearer token. Stateless mode (sessionIdGenerator: undefined)\n * is what makes this safe for many different customers hitting the same\n * running process concurrently — nothing is shared or cached between them.\n */\nasync function handleMcpRequest(req: http.IncomingMessage, res: http.ServerResponse) {\n const token = extractBearerToken(req)\n if (!token) {\n sendJson(res, 401, { error: \"Missing Authorization: Bearer <your-rechnungsapi-token> header\" })\n return\n }\n\n const client = new RechnungsApiClient({\n apiToken: token,\n baseUrl: process.env.RECHNUNGSAPI_BASE_URL,\n v2BaseUrl: process.env.RECHNUNGSAPI_V2_BASE_URL,\n })\n\n const mcpServer = new McpServer({ name: \"rechnungsapi-mcp\", version: \"0.1.0\" })\n registerTools(mcpServer, client)\n\n const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined })\n res.on(\"close\", () => {\n transport.close()\n mcpServer.close()\n })\n\n await mcpServer.connect(transport)\n const body = await readJsonBody(req)\n await transport.handleRequest(req, res, body)\n}\n\nconst server = http.createServer(async (req, res) => {\n if (req.method === \"GET\" && req.url === \"/health\") {\n sendJson(res, 200, { ok: true })\n return\n }\n\n if (req.url !== \"/mcp\") {\n sendJson(res, 404, { error: \"Not found. POST to /mcp.\" })\n return\n }\n\n // This server is stateless (see handleMcpRequest) and never pushes\n // unsolicited server-to-client messages, so the optional GET/SSE stream\n // and DELETE/session-termination parts of the Streamable HTTP spec don't\n // apply here. Reject them fast and explicitly — StreamableHTTPServerTransport\n // has no defined behavior for them with sessionIdGenerator: undefined and\n // was observed to hang indefinitely rather than respond, which made MCP\n // clients' initial reachability probe (a bare GET) time out.\n if (req.method !== \"POST\") {\n sendJson(res, 405, { error: \"Only POST is supported on /mcp (no server-initiated SSE stream).\" })\n return\n }\n\n try {\n await handleMcpRequest(req, res)\n } catch (error) {\n console.error(\"Request failed:\", error)\n if (!res.headersSent) {\n sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) })\n }\n }\n})\n\nserver.listen(PORT, () => {\n console.log(`rechnungsapi-mcp HTTP server listening on port ${PORT}`)\n})\n"],"mappings":";;;;;;AAAA,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,qCAAqC;AAC9C,SAAS,0BAA0B;AAGnC,IAAM,OAAO,OAAO,QAAQ,IAAI,QAAQ,IAAI;AAE5C,SAAS,mBAAmB,KAA0C;AACpE,QAAM,SAAS,IAAI,QAAQ,eAAe;AAC1C,MAAI,CAAC,UAAU,MAAM,QAAQ,MAAM,EAAG,QAAO;AAC7C,QAAM,QAAQ,mBAAmB,KAAK,MAAM;AAC5C,SAAO,QAAQ,MAAM,CAAC,IAAI;AAC5B;AAEA,eAAe,aAAa,KAA6C;AACvE,QAAM,SAAmB,CAAC;AAC1B,mBAAiB,SAAS,IAAK,QAAO,KAAK,KAAe;AAC1D,QAAM,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM;AACjD,SAAO,MAAM,KAAK,MAAM,GAAG,IAAI;AACjC;AAEA,SAAS,SAAS,KAA0B,QAAgB,MAAe;AACzE,MAAI,UAAU,QAAQ,EAAE,gBAAgB,mBAAmB,CAAC;AAC5D,MAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAQA,eAAe,iBAAiB,KAA2B,KAA0B;AACnF,QAAM,QAAQ,mBAAmB,GAAG;AACpC,MAAI,CAAC,OAAO;AACV,aAAS,KAAK,KAAK,EAAE,OAAO,iEAAiE,CAAC;AAC9F;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,mBAAmB;AAAA,IACpC,UAAU;AAAA,IACV,SAAS,QAAQ,IAAI;AAAA,IACrB,WAAW,QAAQ,IAAI;AAAA,EACzB,CAAC;AAED,QAAM,YAAY,IAAI,UAAU,EAAE,MAAM,oBAAoB,SAAS,QAAQ,CAAC;AAC9E,gBAAc,WAAW,MAAM;AAE/B,QAAM,YAAY,IAAI,8BAA8B,EAAE,oBAAoB,OAAU,CAAC;AACrF,MAAI,GAAG,SAAS,MAAM;AACpB,cAAU,MAAM;AAChB,cAAU,MAAM;AAAA,EAClB,CAAC;AAED,QAAM,UAAU,QAAQ,SAAS;AACjC,QAAM,OAAO,MAAM,aAAa,GAAG;AACnC,QAAM,UAAU,cAAc,KAAK,KAAK,IAAI;AAC9C;AAEA,IAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,MAAI,IAAI,WAAW,SAAS,IAAI,QAAQ,WAAW;AACjD,aAAS,KAAK,KAAK,EAAE,IAAI,KAAK,CAAC;AAC/B;AAAA,EACF;AAEA,MAAI,IAAI,QAAQ,QAAQ;AACtB,aAAS,KAAK,KAAK,EAAE,OAAO,2BAA2B,CAAC;AACxD;AAAA,EACF;AASA,MAAI,IAAI,WAAW,QAAQ;AACzB,aAAS,KAAK,KAAK,EAAE,OAAO,mEAAmE,CAAC;AAChG;AAAA,EACF;AAEA,MAAI;AACF,UAAM,iBAAiB,KAAK,GAAG;AAAA,EACjC,SAAS,OAAO;AACd,YAAQ,MAAM,mBAAmB,KAAK;AACtC,QAAI,CAAC,IAAI,aAAa;AACpB,eAAS,KAAK,KAAK,EAAE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAAE,CAAC;AAAA,IACtF;AAAA,EACF;AACF,CAAC;AAED,OAAO,OAAO,MAAM,MAAM;AACxB,UAAQ,IAAI,kDAAkD,IAAI,EAAE;AACtE,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rechnungsapi-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "MCP (Model Context Protocol) server for RechnungsAPI — lets AI agents create, validate, and analyze ZUGFeRD and X-Invoice e-invoices.",
5
5
  "license": "MIT",
6
6
  "author": "Finbc GmbH <support@rechnungsapi.de>",