younium-mcp 1.0.0
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 +66 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +38 -0
- package/dist/client.d.ts +1 -0
- package/dist/client.js +47 -0
- package/dist/executor.d.ts +2 -0
- package/dist/executor.js +45 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +39 -0
- package/dist/spec-loader.d.ts +27 -0
- package/dist/spec-loader.js +196 -0
- package/package.json +36 -0
- package/spec/youniumv2-prod.json +32095 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# younium-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for the [Younium](https://younium.com) subscription management API. Exposes all 159 Younium API operations as tools for Claude Desktop and other MCP clients.
|
|
4
|
+
|
|
5
|
+
## Setup for Claude Desktop
|
|
6
|
+
|
|
7
|
+
1. Find your Claude Desktop config file:
|
|
8
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
9
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
10
|
+
|
|
11
|
+
2. Add the `younium` entry to `mcpServers` (merge with any existing config):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"younium": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "younium-mcp"],
|
|
19
|
+
"env": {
|
|
20
|
+
"YOUNIUM_USERNAME": "user@yourcompany.com",
|
|
21
|
+
"YOUNIUM_PASSWORD": "your-younium-password",
|
|
22
|
+
"YOUNIUM_CLIENT_ID": "your-client-id",
|
|
23
|
+
"YOUNIUM_CLIENT_SECRET": "your-client-secret"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Restart Claude Desktop. You should now be able to ask things like:
|
|
31
|
+
- "List all active subscriptions in Younium"
|
|
32
|
+
- "Get account details for customer X"
|
|
33
|
+
- "Show me invoices from the past 30 days"
|
|
34
|
+
- "Create a new quote for account Y"
|
|
35
|
+
|
|
36
|
+
## Credentials
|
|
37
|
+
|
|
38
|
+
| Env var | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `YOUNIUM_USERNAME` | Your Younium login email |
|
|
41
|
+
| `YOUNIUM_PASSWORD` | Your Younium password |
|
|
42
|
+
| `YOUNIUM_CLIENT_ID` | OAuth2 client ID (from Younium settings) |
|
|
43
|
+
| `YOUNIUM_CLIENT_SECRET` | OAuth2 client secret |
|
|
44
|
+
| `YOUNIUM_LEGAL_ENTITY` | *(optional)* Legal entity ID for multi-tenant setups |
|
|
45
|
+
|
|
46
|
+
## Available tools
|
|
47
|
+
|
|
48
|
+
All 159 Younium API v2.1 endpoints are exposed. Resources include:
|
|
49
|
+
|
|
50
|
+
- **Accounts** — CRUD, payment details, GoCardless/Stripe requests
|
|
51
|
+
- **Subscriptions** — CRUD, amend, cancel, move, charge
|
|
52
|
+
- **Invoices** — list, get, create, post, void, credit notes
|
|
53
|
+
- **Payments** — list, get, create, post
|
|
54
|
+
- **Products / SimpleProducts** — CRUD
|
|
55
|
+
- **Orders / SalesOrders** — CRUD, charges, versions
|
|
56
|
+
- **Quotes** — CRUD, convert to order
|
|
57
|
+
- **Reports** — list, get data
|
|
58
|
+
- **Usage / Measurements** — list, get, import
|
|
59
|
+
- **Webhooks** — list, get, create, delete
|
|
60
|
+
- **Users** — list, get, invite, deactivate
|
|
61
|
+
- **Journals** — list, get, book, reverse
|
|
62
|
+
- And more: Bookings, Metrics, ChartOfAccounts, Currency, PaymentTerms, TaxTemplates, etc.
|
|
63
|
+
|
|
64
|
+
## Requirements
|
|
65
|
+
|
|
66
|
+
- Node.js 18+
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getToken(): Promise<string>;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getToken = getToken;
|
|
4
|
+
const TOKEN_URL = "https://auth.younium.com/connect/token";
|
|
5
|
+
const REFRESH_BUFFER_SECS = 60;
|
|
6
|
+
let cached = null;
|
|
7
|
+
function getEnv(name) {
|
|
8
|
+
const val = process.env[name];
|
|
9
|
+
if (!val)
|
|
10
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
11
|
+
return val;
|
|
12
|
+
}
|
|
13
|
+
async function getToken() {
|
|
14
|
+
const now = Date.now() / 1000;
|
|
15
|
+
if (cached && cached.expiresAt > now + REFRESH_BUFFER_SECS) {
|
|
16
|
+
return cached.value;
|
|
17
|
+
}
|
|
18
|
+
const body = new URLSearchParams({
|
|
19
|
+
grant_type: "password",
|
|
20
|
+
scope: "youniumapi",
|
|
21
|
+
username: getEnv("YOUNIUM_USERNAME"),
|
|
22
|
+
password: getEnv("YOUNIUM_PASSWORD"),
|
|
23
|
+
client_id: getEnv("YOUNIUM_CLIENT_ID"),
|
|
24
|
+
client_secret: getEnv("YOUNIUM_CLIENT_SECRET"),
|
|
25
|
+
});
|
|
26
|
+
const res = await fetch(TOKEN_URL, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
29
|
+
body: body.toString(),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
throw new Error(`Younium auth failed (${res.status}): ${text}`);
|
|
34
|
+
}
|
|
35
|
+
const data = (await res.json());
|
|
36
|
+
cached = { value: data.access_token, expiresAt: now + data.expires_in };
|
|
37
|
+
return cached.value;
|
|
38
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function request(method: string, path: string, query: Record<string, string>, body: unknown | null, idempotencyKey?: string): Promise<unknown>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.request = request;
|
|
4
|
+
const auth_js_1 = require("./auth.js");
|
|
5
|
+
const BASE_URL = "https://api.younium.com";
|
|
6
|
+
const API_VERSION = "2.1";
|
|
7
|
+
async function request(method, path, query, body, idempotencyKey) {
|
|
8
|
+
const token = await (0, auth_js_1.getToken)();
|
|
9
|
+
const url = new URL(BASE_URL + path);
|
|
10
|
+
for (const [k, v] of Object.entries(query)) {
|
|
11
|
+
if (v !== undefined && v !== null && v !== "") {
|
|
12
|
+
url.searchParams.set(k, String(v));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const headers = {
|
|
16
|
+
Authorization: `Bearer ${token}`,
|
|
17
|
+
"api-version": API_VERSION,
|
|
18
|
+
Accept: "application/json",
|
|
19
|
+
};
|
|
20
|
+
if (body !== null) {
|
|
21
|
+
headers["Content-Type"] = "application/json";
|
|
22
|
+
}
|
|
23
|
+
if (idempotencyKey) {
|
|
24
|
+
headers["younium-idempotency-key"] = idempotencyKey;
|
|
25
|
+
}
|
|
26
|
+
const legalEntity = process.env.YOUNIUM_LEGAL_ENTITY;
|
|
27
|
+
if (legalEntity) {
|
|
28
|
+
headers["legal-entity"] = legalEntity;
|
|
29
|
+
}
|
|
30
|
+
const res = await fetch(url.toString(), {
|
|
31
|
+
method: method.toUpperCase(),
|
|
32
|
+
headers,
|
|
33
|
+
body: body !== null ? JSON.stringify(body) : undefined,
|
|
34
|
+
});
|
|
35
|
+
const text = await res.text();
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
return { error: true, status: res.status, body: text };
|
|
38
|
+
}
|
|
39
|
+
if (!text)
|
|
40
|
+
return { success: true };
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(text);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return text;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.execute = execute;
|
|
4
|
+
const client_js_1 = require("./client.js");
|
|
5
|
+
async function execute(tool, args) {
|
|
6
|
+
// Substitute path parameters
|
|
7
|
+
let urlPath = tool.pathTemplate;
|
|
8
|
+
for (const param of tool.pathParams) {
|
|
9
|
+
const val = args[param];
|
|
10
|
+
if (val === undefined || val === null) {
|
|
11
|
+
return JSON.stringify({ error: `Missing required path parameter: ${param}` });
|
|
12
|
+
}
|
|
13
|
+
urlPath = urlPath.replace(`{${param}}`, encodeURIComponent(String(val)));
|
|
14
|
+
}
|
|
15
|
+
// Collect query params
|
|
16
|
+
const query = {};
|
|
17
|
+
for (const param of tool.queryParams) {
|
|
18
|
+
const val = args[param];
|
|
19
|
+
if (val !== undefined && val !== null) {
|
|
20
|
+
query[param] = String(val);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Build body: everything that isn't a path or query param
|
|
24
|
+
let body = null;
|
|
25
|
+
if (tool.hasBody) {
|
|
26
|
+
const paramSet = new Set([...tool.pathParams, ...tool.queryParams]);
|
|
27
|
+
const bodyFields = {};
|
|
28
|
+
for (const [k, v] of Object.entries(args)) {
|
|
29
|
+
if (!paramSet.has(k) && v !== undefined) {
|
|
30
|
+
bodyFields[k] = v;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
body = Object.keys(bodyFields).length > 0 ? bodyFields : null;
|
|
34
|
+
}
|
|
35
|
+
const idempotencyKey = typeof args["younium-idempotency-key"] === "string"
|
|
36
|
+
? args["younium-idempotency-key"]
|
|
37
|
+
: undefined;
|
|
38
|
+
try {
|
|
39
|
+
const result = await (0, client_js_1.request)(tool.method, urlPath, query, body, idempotencyKey);
|
|
40
|
+
return JSON.stringify(result, null, 2);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return JSON.stringify({ error: String(err) });
|
|
44
|
+
}
|
|
45
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const spec_loader_js_1 = require("./spec-loader.js");
|
|
8
|
+
const executor_js_1 = require("./executor.js");
|
|
9
|
+
const tools = (0, spec_loader_js_1.loadTools)();
|
|
10
|
+
// Build a fast lookup map
|
|
11
|
+
const toolMap = new Map(tools.map((t) => [t.name, t]));
|
|
12
|
+
const server = new index_js_1.Server({ name: "younium-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
|
|
13
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
|
|
14
|
+
tools: tools.map((t) => ({
|
|
15
|
+
name: t.name,
|
|
16
|
+
description: t.description,
|
|
17
|
+
inputSchema: t.inputSchema,
|
|
18
|
+
})),
|
|
19
|
+
}));
|
|
20
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (req) => {
|
|
21
|
+
const { name, arguments: args = {} } = req.params;
|
|
22
|
+
const tool = toolMap.get(name);
|
|
23
|
+
if (!tool) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Unknown tool: ${name}` }) }],
|
|
26
|
+
isError: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const result = await (0, executor_js_1.execute)(tool, args);
|
|
30
|
+
return { content: [{ type: "text", text: result }] };
|
|
31
|
+
});
|
|
32
|
+
async function main() {
|
|
33
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
34
|
+
await server.connect(transport);
|
|
35
|
+
}
|
|
36
|
+
main().catch((err) => {
|
|
37
|
+
process.stderr.write(`Fatal: ${err}\n`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface JsonSchema {
|
|
2
|
+
type?: string;
|
|
3
|
+
format?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
nullable?: boolean;
|
|
6
|
+
properties?: Record<string, JsonSchema>;
|
|
7
|
+
items?: JsonSchema;
|
|
8
|
+
required?: string[];
|
|
9
|
+
additionalProperties?: JsonSchema | boolean;
|
|
10
|
+
allOf?: JsonSchema[];
|
|
11
|
+
oneOf?: JsonSchema[];
|
|
12
|
+
anyOf?: JsonSchema[];
|
|
13
|
+
$ref?: string;
|
|
14
|
+
enum?: unknown[];
|
|
15
|
+
}
|
|
16
|
+
export interface ToolDefinition {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
inputSchema: JsonSchema;
|
|
20
|
+
method: string;
|
|
21
|
+
pathTemplate: string;
|
|
22
|
+
pathParams: string[];
|
|
23
|
+
queryParams: string[];
|
|
24
|
+
hasBody: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function loadTools(): ToolDefinition[];
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadTools = loadTools;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
40
|
+
function operationIdToName(operationId) {
|
|
41
|
+
return operationId
|
|
42
|
+
.replace(/[^a-zA-Z0-9]+/g, "_")
|
|
43
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
44
|
+
.toLowerCase()
|
|
45
|
+
.replace(/^_+|_+$/g, "");
|
|
46
|
+
}
|
|
47
|
+
function resolveRef(ref, spec) {
|
|
48
|
+
const parts = ref.replace(/^#\//, "").split("/");
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
let cur = spec;
|
|
51
|
+
for (const p of parts) {
|
|
52
|
+
cur = cur[p];
|
|
53
|
+
if (cur === undefined)
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
return cur;
|
|
57
|
+
}
|
|
58
|
+
function resolveSchema(schema, spec, depth = 0) {
|
|
59
|
+
if (depth > 6)
|
|
60
|
+
return { type: "object" };
|
|
61
|
+
if (schema.$ref) {
|
|
62
|
+
return resolveSchema(resolveRef(schema.$ref, spec), spec, depth + 1);
|
|
63
|
+
}
|
|
64
|
+
const out = { ...schema };
|
|
65
|
+
delete out.$ref;
|
|
66
|
+
if (out.nullable) {
|
|
67
|
+
delete out.nullable;
|
|
68
|
+
}
|
|
69
|
+
if (out.properties) {
|
|
70
|
+
const resolved = {};
|
|
71
|
+
for (const [k, v] of Object.entries(out.properties)) {
|
|
72
|
+
resolved[k] = resolveSchema(v, spec, depth + 1);
|
|
73
|
+
}
|
|
74
|
+
out.properties = resolved;
|
|
75
|
+
}
|
|
76
|
+
if (out.items) {
|
|
77
|
+
out.items = resolveSchema(out.items, spec, depth + 1);
|
|
78
|
+
}
|
|
79
|
+
if (out.allOf) {
|
|
80
|
+
const merged = mergeAllOf(out.allOf, spec, depth);
|
|
81
|
+
delete out.allOf;
|
|
82
|
+
Object.assign(out, merged);
|
|
83
|
+
}
|
|
84
|
+
if (out.oneOf) {
|
|
85
|
+
out.oneOf = out.oneOf.map((s) => resolveSchema(s, spec, depth + 1));
|
|
86
|
+
}
|
|
87
|
+
if (out.anyOf) {
|
|
88
|
+
out.anyOf = out.anyOf.map((s) => resolveSchema(s, spec, depth + 1));
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function mergeAllOf(schemas, spec, depth) {
|
|
93
|
+
const merged = { type: "object", properties: {}, required: [] };
|
|
94
|
+
for (const s of schemas) {
|
|
95
|
+
const resolved = resolveSchema(s, spec, depth + 1);
|
|
96
|
+
if (resolved.properties) {
|
|
97
|
+
Object.assign(merged.properties, resolved.properties);
|
|
98
|
+
}
|
|
99
|
+
if (resolved.required) {
|
|
100
|
+
merged.required = [...(merged.required ?? []), ...resolved.required];
|
|
101
|
+
}
|
|
102
|
+
if (resolved.type && resolved.type !== "object") {
|
|
103
|
+
merged.type = resolved.type;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!merged.properties || Object.keys(merged.properties).length === 0) {
|
|
107
|
+
delete merged.properties;
|
|
108
|
+
}
|
|
109
|
+
if (!merged.required || merged.required.length === 0) {
|
|
110
|
+
delete merged.required;
|
|
111
|
+
}
|
|
112
|
+
return merged;
|
|
113
|
+
}
|
|
114
|
+
function paramSchema(param, spec) {
|
|
115
|
+
const base = param.schema ? resolveSchema(param.schema, spec) : { type: "string" };
|
|
116
|
+
if (param.description) {
|
|
117
|
+
base.description = param.description;
|
|
118
|
+
}
|
|
119
|
+
return base;
|
|
120
|
+
}
|
|
121
|
+
function loadTools() {
|
|
122
|
+
const specPath = path.join(__dirname, "..", "spec", "youniumv2-prod.json");
|
|
123
|
+
const raw = fs.readFileSync(specPath, "utf-8");
|
|
124
|
+
const spec = JSON.parse(raw);
|
|
125
|
+
const tools = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
for (const [pathTemplate, methods] of Object.entries(spec.paths)) {
|
|
128
|
+
for (const method of HTTP_METHODS) {
|
|
129
|
+
const op = methods[method];
|
|
130
|
+
if (!op)
|
|
131
|
+
continue;
|
|
132
|
+
const rawId = op.operationId ?? `${method}_${pathTemplate.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
133
|
+
let name = operationIdToName(rawId);
|
|
134
|
+
// Deduplicate
|
|
135
|
+
if (seen.has(name)) {
|
|
136
|
+
name = `${name}_${method}`;
|
|
137
|
+
}
|
|
138
|
+
seen.add(name);
|
|
139
|
+
const description = [op.summary, op.description]
|
|
140
|
+
.filter(Boolean)
|
|
141
|
+
.join(" — ")
|
|
142
|
+
.slice(0, 400);
|
|
143
|
+
const params = (op.parameters ?? []).filter((p) => p.in !== "header");
|
|
144
|
+
const pathParams = params
|
|
145
|
+
.filter((p) => p.in === "path")
|
|
146
|
+
.map((p) => p.name);
|
|
147
|
+
const queryParams = params
|
|
148
|
+
.filter((p) => p.in === "query")
|
|
149
|
+
.map((p) => p.name);
|
|
150
|
+
// Build combined input schema
|
|
151
|
+
const properties = {};
|
|
152
|
+
const required = [];
|
|
153
|
+
for (const p of params) {
|
|
154
|
+
properties[p.name] = paramSchema(p, spec);
|
|
155
|
+
if (p.required)
|
|
156
|
+
required.push(p.name);
|
|
157
|
+
}
|
|
158
|
+
let hasBody = false;
|
|
159
|
+
if (op.requestBody?.content) {
|
|
160
|
+
const jsonContent = op.requestBody.content["application/json"] ??
|
|
161
|
+
op.requestBody.content["application/merge-patch+json"] ??
|
|
162
|
+
Object.values(op.requestBody.content)[0];
|
|
163
|
+
if (jsonContent?.schema) {
|
|
164
|
+
const bodySchema = resolveSchema(jsonContent.schema, spec);
|
|
165
|
+
if (bodySchema.properties) {
|
|
166
|
+
for (const [k, v] of Object.entries(bodySchema.properties)) {
|
|
167
|
+
properties[k] = v;
|
|
168
|
+
}
|
|
169
|
+
if (bodySchema.required) {
|
|
170
|
+
required.push(...bodySchema.required.filter((r) => !required.includes(r)));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Scalar or array body — wrap it
|
|
175
|
+
properties["body"] = bodySchema;
|
|
176
|
+
}
|
|
177
|
+
hasBody = true;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const inputSchema = { type: "object", properties };
|
|
181
|
+
if (required.length > 0)
|
|
182
|
+
inputSchema.required = required;
|
|
183
|
+
tools.push({
|
|
184
|
+
name,
|
|
185
|
+
description,
|
|
186
|
+
inputSchema,
|
|
187
|
+
method,
|
|
188
|
+
pathTemplate,
|
|
189
|
+
pathParams,
|
|
190
|
+
queryParams,
|
|
191
|
+
hasBody,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return tools;
|
|
196
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "younium-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for the Younium subscription management API",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"spec"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"dev": "ts-node src/index.ts",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
18
|
+
"zod": "^3.23.8"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^22.0.0",
|
|
22
|
+
"typescript": "^5.5.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"younium",
|
|
32
|
+
"mcp",
|
|
33
|
+
"claude",
|
|
34
|
+
"subscription-management"
|
|
35
|
+
]
|
|
36
|
+
}
|