llng-mcp 0.1.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.
Files changed (114) hide show
  1. package/.github/workflows/ci.yml +77 -0
  2. package/.prettierrc +7 -0
  3. package/LICENSE +661 -0
  4. package/README.md +502 -0
  5. package/dist/__tests__/api-transport.test.d.ts +1 -0
  6. package/dist/__tests__/api-transport.test.js +577 -0
  7. package/dist/__tests__/api-transport.test.js.map +1 -0
  8. package/dist/__tests__/config.test.d.ts +1 -0
  9. package/dist/__tests__/config.test.js +472 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/integration/api-mode.test.d.ts +1 -0
  12. package/dist/__tests__/integration/api-mode.test.js +199 -0
  13. package/dist/__tests__/integration/api-mode.test.js.map +1 -0
  14. package/dist/__tests__/integration/oidc-rp.test.d.ts +1 -0
  15. package/dist/__tests__/integration/oidc-rp.test.js +120 -0
  16. package/dist/__tests__/integration/oidc-rp.test.js.map +1 -0
  17. package/dist/__tests__/integration/ssh-mode.test.d.ts +1 -0
  18. package/dist/__tests__/integration/ssh-mode.test.js +101 -0
  19. package/dist/__tests__/integration/ssh-mode.test.js.map +1 -0
  20. package/dist/__tests__/k8s-transport.test.d.ts +1 -0
  21. package/dist/__tests__/k8s-transport.test.js +254 -0
  22. package/dist/__tests__/k8s-transport.test.js.map +1 -0
  23. package/dist/__tests__/oidc-tools.test.d.ts +1 -0
  24. package/dist/__tests__/oidc-tools.test.js +457 -0
  25. package/dist/__tests__/oidc-tools.test.js.map +1 -0
  26. package/dist/__tests__/registry.test.d.ts +1 -0
  27. package/dist/__tests__/registry.test.js +96 -0
  28. package/dist/__tests__/registry.test.js.map +1 -0
  29. package/dist/__tests__/ssh-transport.test.d.ts +1 -0
  30. package/dist/__tests__/ssh-transport.test.js +618 -0
  31. package/dist/__tests__/ssh-transport.test.js.map +1 -0
  32. package/dist/__tests__/tools.test.d.ts +1 -0
  33. package/dist/__tests__/tools.test.js +525 -0
  34. package/dist/__tests__/tools.test.js.map +1 -0
  35. package/dist/config.d.ts +65 -0
  36. package/dist/config.js +506 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.js +42 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/resources/documentation.d.ts +5 -0
  42. package/dist/resources/documentation.js +56 -0
  43. package/dist/resources/documentation.js.map +1 -0
  44. package/dist/tools/cli-utilities.d.ts +3 -0
  45. package/dist/tools/cli-utilities.js +187 -0
  46. package/dist/tools/cli-utilities.js.map +1 -0
  47. package/dist/tools/config.d.ts +6 -0
  48. package/dist/tools/config.js +326 -0
  49. package/dist/tools/config.js.map +1 -0
  50. package/dist/tools/consents.d.ts +3 -0
  51. package/dist/tools/consents.js +39 -0
  52. package/dist/tools/consents.js.map +1 -0
  53. package/dist/tools/instances.d.ts +3 -0
  54. package/dist/tools/instances.js +14 -0
  55. package/dist/tools/instances.js.map +1 -0
  56. package/dist/tools/oidc-rp.d.ts +6 -0
  57. package/dist/tools/oidc-rp.js +246 -0
  58. package/dist/tools/oidc-rp.js.map +1 -0
  59. package/dist/tools/oidc.d.ts +3 -0
  60. package/dist/tools/oidc.js +343 -0
  61. package/dist/tools/oidc.js.map +1 -0
  62. package/dist/tools/secondfactors.d.ts +3 -0
  63. package/dist/tools/secondfactors.js +62 -0
  64. package/dist/tools/secondfactors.js.map +1 -0
  65. package/dist/tools/sessions.d.ts +6 -0
  66. package/dist/tools/sessions.js +300 -0
  67. package/dist/tools/sessions.js.map +1 -0
  68. package/dist/transport/api.d.ts +35 -0
  69. package/dist/transport/api.js +327 -0
  70. package/dist/transport/api.js.map +1 -0
  71. package/dist/transport/interface.d.ts +50 -0
  72. package/dist/transport/interface.js +2 -0
  73. package/dist/transport/interface.js.map +1 -0
  74. package/dist/transport/k8s.d.ts +41 -0
  75. package/dist/transport/k8s.js +303 -0
  76. package/dist/transport/k8s.js.map +1 -0
  77. package/dist/transport/registry.d.ts +20 -0
  78. package/dist/transport/registry.js +91 -0
  79. package/dist/transport/registry.js.map +1 -0
  80. package/dist/transport/ssh.d.ts +37 -0
  81. package/dist/transport/ssh.js +353 -0
  82. package/dist/transport/ssh.js.map +1 -0
  83. package/docker-compose.test.yml +16 -0
  84. package/eslint.config.js +21 -0
  85. package/package.json +38 -0
  86. package/src/__tests__/api-transport.test.ts +746 -0
  87. package/src/__tests__/config.test.ts +587 -0
  88. package/src/__tests__/integration/api-mode.test.ts +229 -0
  89. package/src/__tests__/integration/oidc-rp.test.ts +138 -0
  90. package/src/__tests__/integration/ssh-mode.test.ts +113 -0
  91. package/src/__tests__/k8s-transport.test.ts +342 -0
  92. package/src/__tests__/oidc-tools.test.ts +554 -0
  93. package/src/__tests__/registry.test.ts +110 -0
  94. package/src/__tests__/ssh-transport.test.ts +805 -0
  95. package/src/__tests__/tools.test.ts +735 -0
  96. package/src/config.ts +605 -0
  97. package/src/index.ts +48 -0
  98. package/src/resources/documentation.ts +65 -0
  99. package/src/tools/cli-utilities.ts +207 -0
  100. package/src/tools/config.ts +382 -0
  101. package/src/tools/consents.ts +50 -0
  102. package/src/tools/instances.ts +21 -0
  103. package/src/tools/oidc-rp.ts +299 -0
  104. package/src/tools/oidc.ts +434 -0
  105. package/src/tools/secondfactors.ts +78 -0
  106. package/src/tools/sessions.ts +342 -0
  107. package/src/transport/api.ts +429 -0
  108. package/src/transport/interface.ts +58 -0
  109. package/src/transport/k8s.ts +367 -0
  110. package/src/transport/registry.ts +105 -0
  111. package/src/transport/ssh.ts +430 -0
  112. package/tsconfig.json +16 -0
  113. package/vitest.config.ts +8 -0
  114. package/vitest.integration.config.ts +9 -0
@@ -0,0 +1,21 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TransportRegistry } from "../transport/registry.js";
3
+
4
+ export function registerInstanceTools(server: McpServer, registry: TransportRegistry): void {
5
+ server.tool(
6
+ "llng_instances",
7
+ "List available LLNG instances and their transport mode",
8
+ {},
9
+ async () => {
10
+ const instances = registry.listInstances();
11
+ return {
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text: JSON.stringify(instances, null, 2),
16
+ },
17
+ ],
18
+ };
19
+ },
20
+ );
21
+ }
@@ -0,0 +1,299 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { TransportRegistry } from "../transport/registry.js";
4
+
5
+ const RP_CONFIG_KEYS = [
6
+ "oidcRPMetaDataOptions",
7
+ "oidcRPMetaDataExportedVars",
8
+ "oidcRPMetaDataMacros",
9
+ "oidcRPMetaDataScopeRules",
10
+ "oidcRPMetaDataOptionsExtraClaims",
11
+ ] as const;
12
+
13
+ /**
14
+ * Register OIDC Relying Party management tools
15
+ */
16
+ export function registerOidcRpTools(server: McpServer, registry: TransportRegistry): void {
17
+ // 0. llng_oidc_issuer_enable - Enable OIDC issuer
18
+ server.tool(
19
+ "llng_oidc_issuer_enable",
20
+ "Enable OIDC issuer on the LLNG instance (activates issuerDBOpenIDConnectActivation and generates signing keys)",
21
+ {
22
+ force: z.boolean().optional().describe("Force re-enable even if already activated"),
23
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
24
+ },
25
+ async (args) => {
26
+ try {
27
+ const transport = registry.getTransport(args.instance, "manager");
28
+ const config = await transport.configGet([
29
+ "issuerDBOpenIDConnectActivation",
30
+ "oidcServicePrivateKeySig",
31
+ ]);
32
+
33
+ const isActivated =
34
+ config["issuerDBOpenIDConnectActivation"] === 1 ||
35
+ config["issuerDBOpenIDConnectActivation"] === "1";
36
+ const hasKey =
37
+ typeof config["oidcServicePrivateKeySig"] === "string" &&
38
+ config["oidcServicePrivateKeySig"].length > 0;
39
+
40
+ if (isActivated && hasKey && !args.force) {
41
+ return {
42
+ content: [{ type: "text", text: "OIDC issuer is already enabled." }],
43
+ };
44
+ }
45
+
46
+ await transport.configSet({ issuerDBOpenIDConnectActivation: 1 });
47
+ await transport.execScript("rotateOidcKeys", []);
48
+
49
+ return {
50
+ content: [
51
+ {
52
+ type: "text",
53
+ text: "OIDC issuer enabled successfully. Signing keys have been generated.",
54
+ },
55
+ ],
56
+ };
57
+ } catch (error) {
58
+ return {
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
63
+ },
64
+ ],
65
+ isError: true,
66
+ };
67
+ }
68
+ },
69
+ );
70
+
71
+ // 1. llng_oidc_rp_list - List OIDC Relying Parties
72
+ server.tool(
73
+ "llng_oidc_rp_list",
74
+ "List configured OIDC Relying Parties with their clientID and displayName",
75
+ {
76
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
77
+ },
78
+ async (args) => {
79
+ try {
80
+ const transport = registry.getTransport(args.instance, "manager");
81
+ const data = await transport.configGet(["oidcRPMetaDataOptions"]);
82
+ const options = data["oidcRPMetaDataOptions"];
83
+
84
+ if (!options || typeof options !== "object") {
85
+ return {
86
+ content: [{ type: "text", text: JSON.stringify([], null, 2) }],
87
+ };
88
+ }
89
+
90
+ const list = Object.entries(options).map(([confKey, opts]: [string, any]) => ({
91
+ confKey,
92
+ clientID: opts?.oidcRPMetaDataOptionsClientID || "",
93
+ displayName: opts?.oidcRPMetaDataOptionsDisplayName || "",
94
+ }));
95
+
96
+ return {
97
+ content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
98
+ };
99
+ } catch (error) {
100
+ return {
101
+ content: [
102
+ {
103
+ type: "text",
104
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
105
+ },
106
+ ],
107
+ isError: true,
108
+ };
109
+ }
110
+ },
111
+ );
112
+
113
+ // 2. llng_oidc_rp_get - Get details of an OIDC RP
114
+ server.tool(
115
+ "llng_oidc_rp_get",
116
+ "Get full details of an OIDC Relying Party by confKey",
117
+ {
118
+ confKey: z.string().describe("The RP configuration key (internal identifier)"),
119
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
120
+ },
121
+ async (args) => {
122
+ try {
123
+ const transport = registry.getTransport(args.instance, "manager");
124
+ const data = await transport.configGet([...RP_CONFIG_KEYS]);
125
+
126
+ const result: Record<string, any> = {};
127
+ for (const key of RP_CONFIG_KEYS) {
128
+ const container = data[key];
129
+ if (container && typeof container === "object" && args.confKey in container) {
130
+ result[key] = (container as Record<string, unknown>)[args.confKey];
131
+ }
132
+ }
133
+
134
+ return {
135
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
136
+ };
137
+ } catch (error) {
138
+ return {
139
+ content: [
140
+ {
141
+ type: "text",
142
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
143
+ },
144
+ ],
145
+ isError: true,
146
+ };
147
+ }
148
+ },
149
+ );
150
+
151
+ // 3. llng_oidc_rp_add - Add a new OIDC RP
152
+ server.tool(
153
+ "llng_oidc_rp_add",
154
+ "Add a new OIDC Relying Party",
155
+ {
156
+ confKey: z.string().describe("Internal identifier for the RP"),
157
+ clientId: z.string().describe("OAuth2 client ID"),
158
+ redirectUris: z.string().describe("Redirect URIs (space or newline separated)"),
159
+ clientSecret: z.string().optional().describe("OAuth2 client secret"),
160
+ displayName: z.string().optional().describe("Display name for the RP"),
161
+ exportedVars: z
162
+ .record(z.string(), z.string())
163
+ .optional()
164
+ .describe(
165
+ 'Claim-to-attribute mappings (default: {"name":"cn","preferred_username":"uid","email":"mail"})',
166
+ ),
167
+ extraClaims: z.record(z.string(), z.string()).optional().describe("Extra claims mappings"),
168
+ options: z
169
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
170
+ .optional()
171
+ .describe("Additional raw OIDC RP options"),
172
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
173
+ },
174
+ async (args) => {
175
+ try {
176
+ const transport = registry.getTransport(args.instance, "manager");
177
+
178
+ // Check OIDC issuer prerequisites
179
+ const issuerConfig = await transport.configGet([
180
+ "issuerDBOpenIDConnectActivation",
181
+ "oidcServicePrivateKeySig",
182
+ ]);
183
+ const isActivated =
184
+ issuerConfig["issuerDBOpenIDConnectActivation"] === 1 ||
185
+ issuerConfig["issuerDBOpenIDConnectActivation"] === "1";
186
+ const hasKey =
187
+ typeof issuerConfig["oidcServicePrivateKeySig"] === "string" &&
188
+ issuerConfig["oidcServicePrivateKeySig"].length > 0;
189
+
190
+ if (!isActivated || !hasKey) {
191
+ return {
192
+ content: [
193
+ {
194
+ type: "text" as const,
195
+ text: "Error: OIDC issuer is not enabled or signing keys are missing. Use llng_oidc_issuer_enable first.",
196
+ },
197
+ ],
198
+ isError: true,
199
+ };
200
+ }
201
+
202
+ const rpOptions: Record<string, string | number | boolean> = {
203
+ oidcRPMetaDataOptionsClientID: args.clientId,
204
+ oidcRPMetaDataOptionsRedirectUris: args.redirectUris,
205
+ oidcRPMetaDataOptionsIDTokenSignAlg: "RS256",
206
+ oidcRPMetaDataOptionsAccessTokenClaims: 1,
207
+ ...(args.options || {}),
208
+ };
209
+
210
+ if (args.clientSecret) {
211
+ rpOptions.oidcRPMetaDataOptionsClientSecret = args.clientSecret;
212
+ }
213
+ if (args.displayName) {
214
+ rpOptions.oidcRPMetaDataOptionsDisplayName = args.displayName;
215
+ }
216
+
217
+ const exportedVars = args.exportedVars || {
218
+ name: "cn",
219
+ preferred_username: "uid",
220
+ email: "mail",
221
+ };
222
+
223
+ const mergeData: Record<string, any> = {
224
+ oidcRPMetaDataOptions: {
225
+ [args.confKey]: rpOptions,
226
+ },
227
+ oidcRPMetaDataExportedVars: {
228
+ [args.confKey]: exportedVars,
229
+ },
230
+ };
231
+
232
+ if (args.extraClaims) {
233
+ mergeData.oidcRPMetaDataOptionsExtraClaims = {
234
+ [args.confKey]: args.extraClaims,
235
+ };
236
+ }
237
+
238
+ await transport.configMerge(JSON.stringify(mergeData));
239
+
240
+ return {
241
+ content: [
242
+ {
243
+ type: "text",
244
+ text: `OIDC RP '${args.confKey}' added successfully with clientId '${args.clientId}'.`,
245
+ },
246
+ ],
247
+ };
248
+ } catch (error) {
249
+ return {
250
+ content: [
251
+ {
252
+ type: "text",
253
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
254
+ },
255
+ ],
256
+ isError: true,
257
+ };
258
+ }
259
+ },
260
+ );
261
+
262
+ // 4. llng_oidc_rp_delete - Delete an OIDC RP
263
+ server.tool(
264
+ "llng_oidc_rp_delete",
265
+ "Delete an OIDC Relying Party by confKey",
266
+ {
267
+ confKey: z.string().describe("The RP configuration key to delete"),
268
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
269
+ },
270
+ async (args) => {
271
+ try {
272
+ const transport = registry.getTransport(args.instance, "manager");
273
+
274
+ for (const key of RP_CONFIG_KEYS) {
275
+ await transport.configDelKey(key, args.confKey);
276
+ }
277
+
278
+ return {
279
+ content: [
280
+ {
281
+ type: "text",
282
+ text: `OIDC RP '${args.confKey}' deleted successfully.`,
283
+ },
284
+ ],
285
+ };
286
+ } catch (error) {
287
+ return {
288
+ content: [
289
+ {
290
+ type: "text",
291
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
292
+ },
293
+ ],
294
+ isError: true,
295
+ };
296
+ }
297
+ },
298
+ );
299
+ }