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 @@
1
+ {"version":3,"file":"consents.js","sourceRoot":"","sources":["../../src/tools/consents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,QAA2B;IACjF,MAAM,CAAC,IAAI,CACT,mBAAmB,EACnB,2BAA2B,EAC3B;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;KACzF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;QAAC,OAAO,CAAU,EAAE,CAAC;YACpB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACzF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,+BAA+B,EAC/B;QACE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QACxB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,8CAA8C,CAAC;KACzF,EACD,KAAK,EAAE,MAAM,EAAE,EAAE;QACf,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACzD,MAAM,SAAS,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YACxD,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,MAAM,CAAC,GAAG,CAAC,MAAM,aAAa,EAAE,CAAC;aAC1F,CAAC;QACJ,CAAC;QAAC,OAAO,CAAU,EAAE,CAAC;YACpB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;gBACzF,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TransportRegistry } from "../transport/registry.js";
3
+ export declare function registerInstanceTools(server: McpServer, registry: TransportRegistry): void;
@@ -0,0 +1,14 @@
1
+ export function registerInstanceTools(server, registry) {
2
+ server.tool("llng_instances", "List available LLNG instances and their transport mode", {}, async () => {
3
+ const instances = registry.listInstances();
4
+ return {
5
+ content: [
6
+ {
7
+ type: "text",
8
+ text: JSON.stringify(instances, null, 2),
9
+ },
10
+ ],
11
+ };
12
+ });
13
+ }
14
+ //# sourceMappingURL=instances.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instances.js","sourceRoot":"","sources":["../../src/tools/instances.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,QAA2B;IAClF,MAAM,CAAC,IAAI,CACT,gBAAgB,EAChB,wDAAwD,EACxD,EAAE,EACF,KAAK,IAAI,EAAE;QACT,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC3C,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;iBACzC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TransportRegistry } from "../transport/registry.js";
3
+ /**
4
+ * Register OIDC Relying Party management tools
5
+ */
6
+ export declare function registerOidcRpTools(server: McpServer, registry: TransportRegistry): void;
@@ -0,0 +1,246 @@
1
+ import { z } from "zod";
2
+ const RP_CONFIG_KEYS = [
3
+ "oidcRPMetaDataOptions",
4
+ "oidcRPMetaDataExportedVars",
5
+ "oidcRPMetaDataMacros",
6
+ "oidcRPMetaDataScopeRules",
7
+ "oidcRPMetaDataOptionsExtraClaims",
8
+ ];
9
+ /**
10
+ * Register OIDC Relying Party management tools
11
+ */
12
+ export function registerOidcRpTools(server, registry) {
13
+ // 0. llng_oidc_issuer_enable - Enable OIDC issuer
14
+ server.tool("llng_oidc_issuer_enable", "Enable OIDC issuer on the LLNG instance (activates issuerDBOpenIDConnectActivation and generates signing keys)", {
15
+ force: z.boolean().optional().describe("Force re-enable even if already activated"),
16
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
17
+ }, async (args) => {
18
+ try {
19
+ const transport = registry.getTransport(args.instance, "manager");
20
+ const config = await transport.configGet([
21
+ "issuerDBOpenIDConnectActivation",
22
+ "oidcServicePrivateKeySig",
23
+ ]);
24
+ const isActivated = config["issuerDBOpenIDConnectActivation"] === 1 ||
25
+ config["issuerDBOpenIDConnectActivation"] === "1";
26
+ const hasKey = typeof config["oidcServicePrivateKeySig"] === "string" &&
27
+ config["oidcServicePrivateKeySig"].length > 0;
28
+ if (isActivated && hasKey && !args.force) {
29
+ return {
30
+ content: [{ type: "text", text: "OIDC issuer is already enabled." }],
31
+ };
32
+ }
33
+ await transport.configSet({ issuerDBOpenIDConnectActivation: 1 });
34
+ await transport.execScript("rotateOidcKeys", []);
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: "OIDC issuer enabled successfully. Signing keys have been generated.",
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ catch (error) {
45
+ return {
46
+ content: [
47
+ {
48
+ type: "text",
49
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
50
+ },
51
+ ],
52
+ isError: true,
53
+ };
54
+ }
55
+ });
56
+ // 1. llng_oidc_rp_list - List OIDC Relying Parties
57
+ server.tool("llng_oidc_rp_list", "List configured OIDC Relying Parties with their clientID and displayName", {
58
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
59
+ }, async (args) => {
60
+ try {
61
+ const transport = registry.getTransport(args.instance, "manager");
62
+ const data = await transport.configGet(["oidcRPMetaDataOptions"]);
63
+ const options = data["oidcRPMetaDataOptions"];
64
+ if (!options || typeof options !== "object") {
65
+ return {
66
+ content: [{ type: "text", text: JSON.stringify([], null, 2) }],
67
+ };
68
+ }
69
+ const list = Object.entries(options).map(([confKey, opts]) => ({
70
+ confKey,
71
+ clientID: opts?.oidcRPMetaDataOptionsClientID || "",
72
+ displayName: opts?.oidcRPMetaDataOptionsDisplayName || "",
73
+ }));
74
+ return {
75
+ content: [{ type: "text", text: JSON.stringify(list, null, 2) }],
76
+ };
77
+ }
78
+ catch (error) {
79
+ return {
80
+ content: [
81
+ {
82
+ type: "text",
83
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
84
+ },
85
+ ],
86
+ isError: true,
87
+ };
88
+ }
89
+ });
90
+ // 2. llng_oidc_rp_get - Get details of an OIDC RP
91
+ server.tool("llng_oidc_rp_get", "Get full details of an OIDC Relying Party by confKey", {
92
+ confKey: z.string().describe("The RP configuration key (internal identifier)"),
93
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
94
+ }, async (args) => {
95
+ try {
96
+ const transport = registry.getTransport(args.instance, "manager");
97
+ const data = await transport.configGet([...RP_CONFIG_KEYS]);
98
+ const result = {};
99
+ for (const key of RP_CONFIG_KEYS) {
100
+ const container = data[key];
101
+ if (container && typeof container === "object" && args.confKey in container) {
102
+ result[key] = container[args.confKey];
103
+ }
104
+ }
105
+ return {
106
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
107
+ };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ content: [
112
+ {
113
+ type: "text",
114
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
115
+ },
116
+ ],
117
+ isError: true,
118
+ };
119
+ }
120
+ });
121
+ // 3. llng_oidc_rp_add - Add a new OIDC RP
122
+ server.tool("llng_oidc_rp_add", "Add a new OIDC Relying Party", {
123
+ confKey: z.string().describe("Internal identifier for the RP"),
124
+ clientId: z.string().describe("OAuth2 client ID"),
125
+ redirectUris: z.string().describe("Redirect URIs (space or newline separated)"),
126
+ clientSecret: z.string().optional().describe("OAuth2 client secret"),
127
+ displayName: z.string().optional().describe("Display name for the RP"),
128
+ exportedVars: z
129
+ .record(z.string(), z.string())
130
+ .optional()
131
+ .describe('Claim-to-attribute mappings (default: {"name":"cn","preferred_username":"uid","email":"mail"})'),
132
+ extraClaims: z.record(z.string(), z.string()).optional().describe("Extra claims mappings"),
133
+ options: z
134
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
135
+ .optional()
136
+ .describe("Additional raw OIDC RP options"),
137
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
138
+ }, async (args) => {
139
+ try {
140
+ const transport = registry.getTransport(args.instance, "manager");
141
+ // Check OIDC issuer prerequisites
142
+ const issuerConfig = await transport.configGet([
143
+ "issuerDBOpenIDConnectActivation",
144
+ "oidcServicePrivateKeySig",
145
+ ]);
146
+ const isActivated = issuerConfig["issuerDBOpenIDConnectActivation"] === 1 ||
147
+ issuerConfig["issuerDBOpenIDConnectActivation"] === "1";
148
+ const hasKey = typeof issuerConfig["oidcServicePrivateKeySig"] === "string" &&
149
+ issuerConfig["oidcServicePrivateKeySig"].length > 0;
150
+ if (!isActivated || !hasKey) {
151
+ return {
152
+ content: [
153
+ {
154
+ type: "text",
155
+ text: "Error: OIDC issuer is not enabled or signing keys are missing. Use llng_oidc_issuer_enable first.",
156
+ },
157
+ ],
158
+ isError: true,
159
+ };
160
+ }
161
+ const rpOptions = {
162
+ oidcRPMetaDataOptionsClientID: args.clientId,
163
+ oidcRPMetaDataOptionsRedirectUris: args.redirectUris,
164
+ oidcRPMetaDataOptionsIDTokenSignAlg: "RS256",
165
+ oidcRPMetaDataOptionsAccessTokenClaims: 1,
166
+ ...(args.options || {}),
167
+ };
168
+ if (args.clientSecret) {
169
+ rpOptions.oidcRPMetaDataOptionsClientSecret = args.clientSecret;
170
+ }
171
+ if (args.displayName) {
172
+ rpOptions.oidcRPMetaDataOptionsDisplayName = args.displayName;
173
+ }
174
+ const exportedVars = args.exportedVars || {
175
+ name: "cn",
176
+ preferred_username: "uid",
177
+ email: "mail",
178
+ };
179
+ const mergeData = {
180
+ oidcRPMetaDataOptions: {
181
+ [args.confKey]: rpOptions,
182
+ },
183
+ oidcRPMetaDataExportedVars: {
184
+ [args.confKey]: exportedVars,
185
+ },
186
+ };
187
+ if (args.extraClaims) {
188
+ mergeData.oidcRPMetaDataOptionsExtraClaims = {
189
+ [args.confKey]: args.extraClaims,
190
+ };
191
+ }
192
+ await transport.configMerge(JSON.stringify(mergeData));
193
+ return {
194
+ content: [
195
+ {
196
+ type: "text",
197
+ text: `OIDC RP '${args.confKey}' added successfully with clientId '${args.clientId}'.`,
198
+ },
199
+ ],
200
+ };
201
+ }
202
+ catch (error) {
203
+ return {
204
+ content: [
205
+ {
206
+ type: "text",
207
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
208
+ },
209
+ ],
210
+ isError: true,
211
+ };
212
+ }
213
+ });
214
+ // 4. llng_oidc_rp_delete - Delete an OIDC RP
215
+ server.tool("llng_oidc_rp_delete", "Delete an OIDC Relying Party by confKey", {
216
+ confKey: z.string().describe("The RP configuration key to delete"),
217
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
218
+ }, async (args) => {
219
+ try {
220
+ const transport = registry.getTransport(args.instance, "manager");
221
+ for (const key of RP_CONFIG_KEYS) {
222
+ await transport.configDelKey(key, args.confKey);
223
+ }
224
+ return {
225
+ content: [
226
+ {
227
+ type: "text",
228
+ text: `OIDC RP '${args.confKey}' deleted successfully.`,
229
+ },
230
+ ],
231
+ };
232
+ }
233
+ catch (error) {
234
+ return {
235
+ content: [
236
+ {
237
+ type: "text",
238
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
239
+ },
240
+ ],
241
+ isError: true,
242
+ };
243
+ }
244
+ });
245
+ }
246
+ //# sourceMappingURL=oidc-rp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oidc-rp.js","sourceRoot":"","sources":["../../src/tools/oidc-rp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { TransportRegistry } from "../transport/registry.js";
3
+ export declare function registerOidcTools(server: McpServer, registry: TransportRegistry): void;
@@ -0,0 +1,343 @@
1
+ import { z } from "zod";
2
+ import { randomBytes, createHash } from "crypto";
3
+ // Cache for OIDC discovery documents, keyed by issuer with TTL
4
+ const discoveryCache = new Map();
5
+ const DISCOVERY_CACHE_TTL_MS = 3600_000; // 1 hour
6
+ async function getDiscoveryMetadata(config) {
7
+ const cached = discoveryCache.get(config.issuer);
8
+ if (cached && Date.now() - cached.fetchedAt < DISCOVERY_CACHE_TTL_MS) {
9
+ return cached.metadata;
10
+ }
11
+ const url = `${config.issuer}/.well-known/openid-configuration`;
12
+ const response = await fetch(url);
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to fetch discovery metadata: ${response.status} ${response.statusText}`);
15
+ }
16
+ const metadata = await response.json();
17
+ discoveryCache.set(config.issuer, { metadata, fetchedAt: Date.now() });
18
+ return metadata;
19
+ }
20
+ function generateCodeVerifier() {
21
+ return randomBytes(32).toString("base64url");
22
+ }
23
+ function generateCodeChallenge(verifier) {
24
+ return createHash("sha256").update(verifier).digest("base64url");
25
+ }
26
+ function generateState() {
27
+ return randomBytes(16).toString("base64url");
28
+ }
29
+ function isUrlSafe(url) {
30
+ try {
31
+ const parsed = new URL(url);
32
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
33
+ return false;
34
+ }
35
+ const hostname = parsed.hostname;
36
+ // Block private/link-local/loopback IPs
37
+ if (hostname === "localhost" ||
38
+ hostname.startsWith("127.") ||
39
+ hostname === "[::1]" ||
40
+ hostname.startsWith("10.") ||
41
+ hostname.startsWith("192.168.") ||
42
+ hostname.startsWith("169.254.") ||
43
+ hostname.startsWith("0.") ||
44
+ /^172\.(1[6-9]|2\d|3[01])\./.test(hostname)) {
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ function base64UrlDecode(str) {
54
+ // Convert base64url to base64
55
+ let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
56
+ // Add padding if needed
57
+ base64 += "=".repeat((4 - (base64.length % 4)) % 4);
58
+ return Buffer.from(base64, "base64").toString("utf-8");
59
+ }
60
+ export function registerOidcTools(server, registry) {
61
+ server.tool("llng_oidc_metadata", "Fetch OIDC discovery metadata", {
62
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
63
+ }, async (params) => {
64
+ try {
65
+ const config = registry.getOidcConfig(params.instance);
66
+ if (!config) {
67
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
68
+ }
69
+ const metadata = await getDiscoveryMetadata(config);
70
+ return { content: [{ type: "text", text: JSON.stringify(metadata, null, 2) }] };
71
+ }
72
+ catch (e) {
73
+ return {
74
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
75
+ isError: true,
76
+ };
77
+ }
78
+ });
79
+ server.tool("llng_oidc_authorize", "Get authorization URL with PKCE", {
80
+ scope: z.string().optional(),
81
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
82
+ }, async (params) => {
83
+ try {
84
+ const config = registry.getOidcConfig(params.instance);
85
+ if (!config) {
86
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
87
+ }
88
+ const metadata = await getDiscoveryMetadata(config);
89
+ const codeVerifier = generateCodeVerifier();
90
+ const codeChallenge = generateCodeChallenge(codeVerifier);
91
+ const state = generateState();
92
+ const scope = params.scope || config.scope;
93
+ const authUrl = new URL(metadata.authorization_endpoint);
94
+ authUrl.searchParams.set("response_type", "code");
95
+ authUrl.searchParams.set("client_id", config.clientId);
96
+ authUrl.searchParams.set("redirect_uri", config.redirectUri);
97
+ authUrl.searchParams.set("scope", scope);
98
+ authUrl.searchParams.set("code_challenge", codeChallenge);
99
+ authUrl.searchParams.set("code_challenge_method", "S256");
100
+ authUrl.searchParams.set("state", state);
101
+ const result = {
102
+ url: authUrl.toString(),
103
+ code_verifier: codeVerifier,
104
+ state: state,
105
+ };
106
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
107
+ }
108
+ catch (e) {
109
+ return {
110
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
111
+ isError: true,
112
+ };
113
+ }
114
+ });
115
+ server.tool("llng_oidc_tokens", "Exchange authorization code for tokens", {
116
+ code: z.string(),
117
+ code_verifier: z.string(),
118
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
119
+ }, async (params) => {
120
+ try {
121
+ const config = registry.getOidcConfig(params.instance);
122
+ if (!config) {
123
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
124
+ }
125
+ const metadata = await getDiscoveryMetadata(config);
126
+ const body = new URLSearchParams();
127
+ body.set("grant_type", "authorization_code");
128
+ body.set("code", params.code);
129
+ body.set("redirect_uri", config.redirectUri);
130
+ body.set("client_id", config.clientId);
131
+ body.set("code_verifier", params.code_verifier);
132
+ if (config.clientSecret) {
133
+ body.set("client_secret", config.clientSecret);
134
+ }
135
+ const response = await fetch(metadata.token_endpoint, {
136
+ method: "POST",
137
+ headers: {
138
+ "Content-Type": "application/x-www-form-urlencoded",
139
+ },
140
+ body: body.toString(),
141
+ });
142
+ const result = await response.json();
143
+ if (!response.ok) {
144
+ return {
145
+ content: [{ type: "text", text: `Error: ${JSON.stringify(result)}` }],
146
+ isError: true,
147
+ };
148
+ }
149
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
150
+ }
151
+ catch (e) {
152
+ return {
153
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
154
+ isError: true,
155
+ };
156
+ }
157
+ });
158
+ server.tool("llng_oidc_userinfo", "Get user info from OIDC provider", {
159
+ access_token: z.string(),
160
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
161
+ }, async (params) => {
162
+ try {
163
+ const config = registry.getOidcConfig(params.instance);
164
+ if (!config) {
165
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
166
+ }
167
+ const metadata = await getDiscoveryMetadata(config);
168
+ const response = await fetch(metadata.userinfo_endpoint, {
169
+ headers: {
170
+ Authorization: `Bearer ${params.access_token}`,
171
+ },
172
+ });
173
+ const result = await response.json();
174
+ if (!response.ok) {
175
+ return {
176
+ content: [{ type: "text", text: `Error: ${JSON.stringify(result)}` }],
177
+ isError: true,
178
+ };
179
+ }
180
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
181
+ }
182
+ catch (e) {
183
+ return {
184
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
185
+ isError: true,
186
+ };
187
+ }
188
+ });
189
+ server.tool("llng_oidc_introspect", "Introspect an access token", {
190
+ token: z.string(),
191
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
192
+ }, async (params) => {
193
+ try {
194
+ const config = registry.getOidcConfig(params.instance);
195
+ if (!config) {
196
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
197
+ }
198
+ const metadata = await getDiscoveryMetadata(config);
199
+ if (!metadata.introspection_endpoint) {
200
+ return {
201
+ content: [{ type: "text", text: "Error: Introspection endpoint not supported" }],
202
+ isError: true,
203
+ };
204
+ }
205
+ const body = new URLSearchParams();
206
+ body.set("token", params.token);
207
+ body.set("client_id", config.clientId);
208
+ if (config.clientSecret) {
209
+ body.set("client_secret", config.clientSecret);
210
+ }
211
+ const response = await fetch(metadata.introspection_endpoint, {
212
+ method: "POST",
213
+ headers: {
214
+ "Content-Type": "application/x-www-form-urlencoded",
215
+ },
216
+ body: body.toString(),
217
+ });
218
+ const result = await response.json();
219
+ if (!response.ok) {
220
+ return {
221
+ content: [{ type: "text", text: `Error: ${JSON.stringify(result)}` }],
222
+ isError: true,
223
+ };
224
+ }
225
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
226
+ }
227
+ catch (e) {
228
+ return {
229
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
230
+ isError: true,
231
+ };
232
+ }
233
+ });
234
+ server.tool("llng_oidc_refresh", "Refresh an access token", {
235
+ refresh_token: z.string(),
236
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
237
+ }, async (params) => {
238
+ try {
239
+ const config = registry.getOidcConfig(params.instance);
240
+ if (!config) {
241
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
242
+ }
243
+ const metadata = await getDiscoveryMetadata(config);
244
+ const body = new URLSearchParams();
245
+ body.set("grant_type", "refresh_token");
246
+ body.set("refresh_token", params.refresh_token);
247
+ body.set("client_id", config.clientId);
248
+ if (config.clientSecret) {
249
+ body.set("client_secret", config.clientSecret);
250
+ }
251
+ const response = await fetch(metadata.token_endpoint, {
252
+ method: "POST",
253
+ headers: {
254
+ "Content-Type": "application/x-www-form-urlencoded",
255
+ },
256
+ body: body.toString(),
257
+ });
258
+ const result = await response.json();
259
+ if (!response.ok) {
260
+ return {
261
+ content: [{ type: "text", text: `Error: ${JSON.stringify(result)}` }],
262
+ isError: true,
263
+ };
264
+ }
265
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
266
+ }
267
+ catch (e) {
268
+ return {
269
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
270
+ isError: true,
271
+ };
272
+ }
273
+ });
274
+ server.tool("llng_oidc_whoami", "Decode ID token to show identity (WARNING: signature is NOT verified, for debugging only)", {
275
+ id_token: z.string(),
276
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
277
+ }, async (params) => {
278
+ try {
279
+ const config = registry.getOidcConfig(params.instance);
280
+ if (!config) {
281
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
282
+ }
283
+ const parts = params.id_token.split(".");
284
+ if (parts.length !== 3) {
285
+ return { content: [{ type: "text", text: "Error: Invalid JWT format" }], isError: true };
286
+ }
287
+ const payload = JSON.parse(base64UrlDecode(parts[1]));
288
+ const result = {
289
+ _warning: "UNVERIFIED - JWT signature was NOT checked. Do not trust these claims for authorization decisions.",
290
+ ...payload,
291
+ };
292
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
293
+ }
294
+ catch (e) {
295
+ return {
296
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
297
+ isError: true,
298
+ };
299
+ }
300
+ });
301
+ server.tool("llng_oidc_check_auth", "Check if a URL requires authentication (only public URLs allowed, private/internal IPs blocked)", {
302
+ url: z.string().url(),
303
+ access_token: z.string(),
304
+ instance: z.string().optional().describe("LLNG instance name (uses default if omitted)"),
305
+ }, async (params) => {
306
+ try {
307
+ const config = registry.getOidcConfig(params.instance);
308
+ if (!config) {
309
+ return { content: [{ type: "text", text: "Error: OIDC not configured" }], isError: true };
310
+ }
311
+ if (!isUrlSafe(params.url)) {
312
+ return {
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: "Error: URL must be a public HTTP(S) URL. Private, loopback, and link-local addresses are blocked.",
317
+ },
318
+ ],
319
+ isError: true,
320
+ };
321
+ }
322
+ const response = await fetch(params.url, {
323
+ headers: {
324
+ Authorization: `Bearer ${params.access_token}`,
325
+ },
326
+ redirect: "manual",
327
+ });
328
+ const result = {
329
+ status: response.status,
330
+ statusText: response.statusText,
331
+ headers: Object.fromEntries(response.headers.entries()),
332
+ };
333
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
334
+ }
335
+ catch (e) {
336
+ return {
337
+ content: [{ type: "text", text: `Error: ${e instanceof Error ? e.message : String(e)}` }],
338
+ isError: true,
339
+ };
340
+ }
341
+ });
342
+ }
343
+ //# sourceMappingURL=oidc.js.map