strapi-oauth-mcp-manager 0.1.3 → 0.1.5

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.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const node_crypto = require("node:crypto");
3
3
  const PLUGIN_ID = "strapi-oauth-mcp-manager";
4
- const PLUGIN_UID$3 = `plugin::${PLUGIN_ID}`;
4
+ const PLUGIN_UID$2 = `plugin::${PLUGIN_ID}`;
5
5
  const MCP_ENDPOINT_PATTERN = /^\/api\/[^/]+\/mcp(\/.*)?$/;
6
6
  function extractBearerToken(authHeader) {
7
7
  if (!authHeader?.startsWith("Bearer ")) {
@@ -22,7 +22,7 @@ function buildWwwAuthenticateHeader(ctx, strapi) {
22
22
  }
23
23
  async function validateOAuthToken(token, strapi) {
24
24
  try {
25
- const tokenRecord = await strapi.documents(`${PLUGIN_UID$3}.mcp-oauth-token`).findFirst({
25
+ const tokenRecord = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).findFirst({
26
26
  filters: { accessToken: token, revoked: false }
27
27
  });
28
28
  if (!tokenRecord) {
@@ -31,7 +31,7 @@ async function validateOAuthToken(token, strapi) {
31
31
  if (new Date(tokenRecord.expiresAt) < /* @__PURE__ */ new Date()) {
32
32
  return { valid: false, error: "Token expired" };
33
33
  }
34
- const client = await strapi.documents(`${PLUGIN_UID$3}.mcp-oauth-client`).findFirst({
34
+ const client = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-client`).findFirst({
35
35
  filters: { clientId: tokenRecord.clientId }
36
36
  });
37
37
  if (!client) {
@@ -93,18 +93,18 @@ const config = {
93
93
  validator() {
94
94
  }
95
95
  };
96
- const kind$3 = "collectionType";
97
- const collectionName$3 = "mcp_oauth_clients";
98
- const info$3 = {
96
+ const kind$2 = "collectionType";
97
+ const collectionName$2 = "mcp_oauth_clients";
98
+ const info$2 = {
99
99
  singularName: "mcp-oauth-client",
100
100
  pluralName: "mcp-oauth-clients",
101
101
  displayName: "MCP OAuth Client",
102
102
  description: "OAuth 2.0 clients for MCP authentication"
103
103
  };
104
- const options$3 = {
104
+ const options$2 = {
105
105
  draftAndPublish: false
106
106
  };
107
- const pluginOptions$3 = {
107
+ const pluginOptions$2 = {
108
108
  "content-manager": {
109
109
  visible: true
110
110
  },
@@ -112,7 +112,7 @@ const pluginOptions$3 = {
112
112
  visible: true
113
113
  }
114
114
  };
115
- const attributes$3 = {
115
+ const attributes$2 = {
116
116
  name: {
117
117
  type: "string",
118
118
  required: true
@@ -132,7 +132,7 @@ const attributes$3 = {
132
132
  required: true
133
133
  },
134
134
  strapiApiToken: {
135
- type: "string",
135
+ type: "text",
136
136
  required: true,
137
137
  "private": true
138
138
  },
@@ -145,25 +145,25 @@ const attributes$3 = {
145
145
  }
146
146
  };
147
147
  const mcpOauthClient = {
148
- kind: kind$3,
149
- collectionName: collectionName$3,
150
- info: info$3,
151
- options: options$3,
152
- pluginOptions: pluginOptions$3,
153
- attributes: attributes$3
148
+ kind: kind$2,
149
+ collectionName: collectionName$2,
150
+ info: info$2,
151
+ options: options$2,
152
+ pluginOptions: pluginOptions$2,
153
+ attributes: attributes$2
154
154
  };
155
- const kind$2 = "collectionType";
156
- const collectionName$2 = "mcp_oauth_codes";
157
- const info$2 = {
155
+ const kind$1 = "collectionType";
156
+ const collectionName$1 = "mcp_oauth_codes";
157
+ const info$1 = {
158
158
  singularName: "mcp-oauth-code",
159
159
  pluralName: "mcp-oauth-codes",
160
160
  displayName: "MCP OAuth Code",
161
161
  description: "OAuth 2.0 authorization codes"
162
162
  };
163
- const options$2 = {
163
+ const options$1 = {
164
164
  draftAndPublish: false
165
165
  };
166
- const pluginOptions$2 = {
166
+ const pluginOptions$1 = {
167
167
  "content-manager": {
168
168
  visible: false
169
169
  },
@@ -171,7 +171,7 @@ const pluginOptions$2 = {
171
171
  visible: false
172
172
  }
173
173
  };
174
- const attributes$2 = {
174
+ const attributes$1 = {
175
175
  code: {
176
176
  type: "string",
177
177
  required: true,
@@ -201,25 +201,25 @@ const attributes$2 = {
201
201
  }
202
202
  };
203
203
  const mcpOauthCode = {
204
- kind: kind$2,
205
- collectionName: collectionName$2,
206
- info: info$2,
207
- options: options$2,
208
- pluginOptions: pluginOptions$2,
209
- attributes: attributes$2
204
+ kind: kind$1,
205
+ collectionName: collectionName$1,
206
+ info: info$1,
207
+ options: options$1,
208
+ pluginOptions: pluginOptions$1,
209
+ attributes: attributes$1
210
210
  };
211
- const kind$1 = "collectionType";
212
- const collectionName$1 = "mcp_oauth_tokens";
213
- const info$1 = {
211
+ const kind = "collectionType";
212
+ const collectionName = "mcp_oauth_tokens";
213
+ const info = {
214
214
  singularName: "mcp-oauth-token",
215
215
  pluralName: "mcp-oauth-tokens",
216
216
  displayName: "MCP OAuth Token",
217
217
  description: "OAuth 2.0 access and refresh tokens"
218
218
  };
219
- const options$1 = {
219
+ const options = {
220
220
  draftAndPublish: false
221
221
  };
222
- const pluginOptions$1 = {
222
+ const pluginOptions = {
223
223
  "content-manager": {
224
224
  visible: false
225
225
  },
@@ -227,7 +227,7 @@ const pluginOptions$1 = {
227
227
  visible: false
228
228
  }
229
229
  };
230
- const attributes$1 = {
230
+ const attributes = {
231
231
  accessToken: {
232
232
  type: "string",
233
233
  required: true,
@@ -258,55 +258,6 @@ const attributes$1 = {
258
258
  }
259
259
  };
260
260
  const mcpOauthToken = {
261
- kind: kind$1,
262
- collectionName: collectionName$1,
263
- info: info$1,
264
- options: options$1,
265
- pluginOptions: pluginOptions$1,
266
- attributes: attributes$1
267
- };
268
- const kind = "collectionType";
269
- const collectionName = "mcp_endpoints";
270
- const info = {
271
- singularName: "mcp-endpoint",
272
- pluralName: "mcp-endpoints",
273
- displayName: "MCP Endpoint",
274
- description: "Registered MCP endpoints protected by OAuth"
275
- };
276
- const options = {
277
- draftAndPublish: false
278
- };
279
- const pluginOptions = {
280
- "content-manager": {
281
- visible: true
282
- },
283
- "content-type-builder": {
284
- visible: true
285
- }
286
- };
287
- const attributes = {
288
- name: {
289
- type: "string",
290
- required: true
291
- },
292
- pluginId: {
293
- type: "string",
294
- required: true
295
- },
296
- path: {
297
- type: "string",
298
- required: true,
299
- unique: true
300
- },
301
- description: {
302
- type: "text"
303
- },
304
- active: {
305
- type: "boolean",
306
- "default": true
307
- }
308
- };
309
- const mcpEndpoint = {
310
261
  kind,
311
262
  collectionName,
312
263
  info,
@@ -317,10 +268,9 @@ const mcpEndpoint = {
317
268
  const contentTypes = {
318
269
  "mcp-oauth-client": { schema: mcpOauthClient },
319
270
  "mcp-oauth-code": { schema: mcpOauthCode },
320
- "mcp-oauth-token": { schema: mcpOauthToken },
321
- "mcp-endpoint": { schema: mcpEndpoint }
271
+ "mcp-oauth-token": { schema: mcpOauthToken }
322
272
  };
323
- const PLUGIN_UID$2 = `plugin::${PLUGIN_ID}`;
273
+ const PLUGIN_UID$1 = `plugin::${PLUGIN_ID}`;
324
274
  function getBaseUrl(ctx, strapi) {
325
275
  const forwardedProto = ctx.request.headers["x-forwarded-proto"] || ctx.protocol;
326
276
  const forwardedHost = ctx.request.headers["x-forwarded-host"] || ctx.request.headers["host"];
@@ -357,17 +307,15 @@ const oauthController = ({ strapi }) => ({
357
307
  /**
358
308
  * OAuth 2.0 Protected Resource Metadata (RFC 9728)
359
309
  * GET /.well-known/oauth-protected-resource
310
+ *
311
+ * Convention-based protection: all /api/{plugin}/mcp endpoints are protected.
360
312
  */
361
313
  async protectedResource(ctx) {
362
314
  const baseUrl = getBaseUrl(ctx, strapi);
363
315
  const pluginPath = `/api/${PLUGIN_ID}`;
364
316
  const authServer = `${baseUrl}${pluginPath}`;
365
- const endpoints = await strapi.documents(`${PLUGIN_UID$2}.mcp-endpoint`).findMany({
366
- filters: { active: true }
367
- });
368
- const resources = endpoints.map((e) => `${baseUrl}${e.path}`);
369
317
  ctx.body = {
370
- resource: resources.length === 1 ? resources[0] : resources,
318
+ resource: `${baseUrl}/api`,
371
319
  authorization_servers: [authServer],
372
320
  bearer_methods_supported: ["header"]
373
321
  };
@@ -393,7 +341,7 @@ const oauthController = ({ strapi }) => ({
393
341
  ctx.body = { error: "unsupported_response_type", error_description: "Only code response type is supported" };
394
342
  return;
395
343
  }
396
- const client = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-client`).findFirst({
344
+ const client = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-client`).findFirst({
397
345
  filters: { clientId: client_id, active: true }
398
346
  });
399
347
  if (!client) {
@@ -422,7 +370,7 @@ const oauthController = ({ strapi }) => ({
422
370
  }
423
371
  const code = node_crypto.randomBytes(32).toString("hex");
424
372
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
425
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-code`).create({
373
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).create({
426
374
  data: {
427
375
  code,
428
376
  clientId: client_id,
@@ -460,7 +408,7 @@ const oauthController = ({ strapi }) => ({
460
408
  ctx.body = { error: "invalid_client", error_description: "Client authentication required" };
461
409
  return;
462
410
  }
463
- const client = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-client`).findFirst({
411
+ const client = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-client`).findFirst({
464
412
  filters: { clientId: authClientId, active: true }
465
413
  });
466
414
  if (!client || authClientSecret && client.clientSecret !== authClientSecret) {
@@ -484,7 +432,7 @@ async function handleAuthorizationCodeGrant(ctx, strapi, client, code, redirect_
484
432
  ctx.body = { error: "invalid_request", error_description: "code is required" };
485
433
  return;
486
434
  }
487
- const authCode = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-code`).findFirst({
435
+ const authCode = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).findFirst({
488
436
  filters: { code, clientId: client.clientId, used: false }
489
437
  });
490
438
  if (!authCode) {
@@ -502,7 +450,7 @@ async function handleAuthorizationCodeGrant(ctx, strapi, client, code, redirect_
502
450
  ctx.body = { error: "invalid_grant", error_description: "redirect_uri mismatch" };
503
451
  return;
504
452
  }
505
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-code`).update({
453
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).update({
506
454
  documentId: authCode.documentId,
507
455
  data: { used: true }
508
456
  });
@@ -510,7 +458,7 @@ async function handleAuthorizationCodeGrant(ctx, strapi, client, code, redirect_
510
458
  const refreshToken = node_crypto.randomBytes(32).toString("hex");
511
459
  const expiresIn = 3600;
512
460
  const refreshExpiresIn = 30 * 24 * 3600;
513
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).create({
461
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).create({
514
462
  data: {
515
463
  accessToken,
516
464
  refreshToken,
@@ -533,7 +481,7 @@ async function handleRefreshTokenGrant(ctx, strapi, client, refreshToken) {
533
481
  ctx.body = { error: "invalid_request", error_description: "refresh_token is required" };
534
482
  return;
535
483
  }
536
- const token = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).findFirst({
484
+ const token = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findFirst({
537
485
  filters: { refreshToken, clientId: client.clientId, revoked: false }
538
486
  });
539
487
  if (!token) {
@@ -546,7 +494,7 @@ async function handleRefreshTokenGrant(ctx, strapi, client, refreshToken) {
546
494
  ctx.body = { error: "invalid_grant", error_description: "Refresh token expired" };
547
495
  return;
548
496
  }
549
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).update({
497
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).update({
550
498
  documentId: token.documentId,
551
499
  data: { revoked: true }
552
500
  });
@@ -554,7 +502,7 @@ async function handleRefreshTokenGrant(ctx, strapi, client, refreshToken) {
554
502
  const newRefreshToken = node_crypto.randomBytes(32).toString("hex");
555
503
  const expiresIn = 3600;
556
504
  const refreshExpiresIn = 30 * 24 * 3600;
557
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).create({
505
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).create({
558
506
  data: {
559
507
  accessToken: newAccessToken,
560
508
  refreshToken: newRefreshToken,
@@ -631,14 +579,14 @@ const routes = {
631
579
  routes: [...admin]
632
580
  }
633
581
  };
634
- const PLUGIN_UID$1 = `plugin::${PLUGIN_ID}`;
582
+ const PLUGIN_UID = `plugin::${PLUGIN_ID}`;
635
583
  const oauthService = ({ strapi }) => ({
636
584
  /**
637
585
  * Validate an OAuth access token
638
586
  */
639
587
  async validateToken(accessToken) {
640
588
  try {
641
- const tokenRecord = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findFirst({
589
+ const tokenRecord = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).findFirst({
642
590
  filters: { accessToken, revoked: false }
643
591
  });
644
592
  if (!tokenRecord) {
@@ -647,7 +595,7 @@ const oauthService = ({ strapi }) => ({
647
595
  if (new Date(tokenRecord.expiresAt) < /* @__PURE__ */ new Date()) {
648
596
  return { valid: false, error: "Token expired" };
649
597
  }
650
- const client = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-client`).findFirst({
598
+ const client = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-client`).findFirst({
651
599
  filters: { clientId: tokenRecord.clientId }
652
600
  });
653
601
  if (!client) {
@@ -667,11 +615,11 @@ const oauthService = ({ strapi }) => ({
667
615
  * Revoke all tokens for a client
668
616
  */
669
617
  async revokeClientTokens(clientId) {
670
- const tokens = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findMany({
618
+ const tokens = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).findMany({
671
619
  filters: { clientId, revoked: false }
672
620
  });
673
621
  for (const token of tokens) {
674
- await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).update({
622
+ await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).update({
675
623
  documentId: token.documentId,
676
624
  data: { revoked: true }
677
625
  });
@@ -683,19 +631,19 @@ const oauthService = ({ strapi }) => ({
683
631
  */
684
632
  async cleanupExpired() {
685
633
  const now = (/* @__PURE__ */ new Date()).toISOString();
686
- const expiredCodes = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).findMany({
634
+ const expiredCodes = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-code`).findMany({
687
635
  filters: { expiresAt: { $lt: now } }
688
636
  });
689
637
  for (const code of expiredCodes) {
690
- await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).delete({
638
+ await strapi.documents(`${PLUGIN_UID}.mcp-oauth-code`).delete({
691
639
  documentId: code.documentId
692
640
  });
693
641
  }
694
- const expiredTokens = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findMany({
642
+ const expiredTokens = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).findMany({
695
643
  filters: { refreshExpiresAt: { $lt: now } }
696
644
  });
697
645
  for (const token of expiredTokens) {
698
- await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).delete({
646
+ await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).delete({
699
647
  documentId: token.documentId
700
648
  });
701
649
  }
@@ -705,80 +653,8 @@ const oauthService = ({ strapi }) => ({
705
653
  };
706
654
  }
707
655
  });
708
- const PLUGIN_UID = `plugin::${PLUGIN_ID}`;
709
- const endpointService = ({ strapi }) => ({
710
- /**
711
- * Register an MCP endpoint for OAuth protection
712
- */
713
- async register(endpoint) {
714
- const existing = await strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findFirst({
715
- filters: { path: endpoint.path }
716
- });
717
- if (existing) {
718
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).update({
719
- documentId: existing.documentId,
720
- data: {
721
- name: endpoint.name,
722
- pluginId: endpoint.pluginId,
723
- description: endpoint.description,
724
- active: true
725
- }
726
- });
727
- }
728
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).create({
729
- data: {
730
- name: endpoint.name,
731
- pluginId: endpoint.pluginId,
732
- path: endpoint.path,
733
- description: endpoint.description,
734
- active: true
735
- }
736
- });
737
- },
738
- /**
739
- * Unregister an MCP endpoint
740
- */
741
- async unregister(path) {
742
- const endpoint = await strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findFirst({
743
- filters: { path }
744
- });
745
- if (!endpoint) {
746
- return false;
747
- }
748
- await strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).update({
749
- documentId: endpoint.documentId,
750
- data: { active: false }
751
- });
752
- return true;
753
- },
754
- /**
755
- * Get all registered endpoints
756
- */
757
- async getAll(activeOnly = true) {
758
- const filters = activeOnly ? { active: true } : {};
759
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findMany({ filters });
760
- },
761
- /**
762
- * Get endpoints for a specific plugin
763
- */
764
- async getByPlugin(pluginId, activeOnly = true) {
765
- const filters = { pluginId };
766
- if (activeOnly) {
767
- filters.active = true;
768
- }
769
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findMany({ filters });
770
- },
771
- /**
772
- * Check if a path is a protected endpoint
773
- */
774
- async isProtected(path) {
775
- const endpoints = await this.getAll(true);
776
- return endpoints.some((e) => path.includes(e.path));
777
- }
778
- });
779
656
  const services = {
780
- oauth: oauthService,
781
- endpoint: endpointService
657
+ oauth: oauthService
782
658
  };
783
659
  const index = {
784
660
  register,
@@ -1,6 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  const PLUGIN_ID = "strapi-oauth-mcp-manager";
3
- const PLUGIN_UID$3 = `plugin::${PLUGIN_ID}`;
3
+ const PLUGIN_UID$2 = `plugin::${PLUGIN_ID}`;
4
4
  const MCP_ENDPOINT_PATTERN = /^\/api\/[^/]+\/mcp(\/.*)?$/;
5
5
  function extractBearerToken(authHeader) {
6
6
  if (!authHeader?.startsWith("Bearer ")) {
@@ -21,7 +21,7 @@ function buildWwwAuthenticateHeader(ctx, strapi) {
21
21
  }
22
22
  async function validateOAuthToken(token, strapi) {
23
23
  try {
24
- const tokenRecord = await strapi.documents(`${PLUGIN_UID$3}.mcp-oauth-token`).findFirst({
24
+ const tokenRecord = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).findFirst({
25
25
  filters: { accessToken: token, revoked: false }
26
26
  });
27
27
  if (!tokenRecord) {
@@ -30,7 +30,7 @@ async function validateOAuthToken(token, strapi) {
30
30
  if (new Date(tokenRecord.expiresAt) < /* @__PURE__ */ new Date()) {
31
31
  return { valid: false, error: "Token expired" };
32
32
  }
33
- const client = await strapi.documents(`${PLUGIN_UID$3}.mcp-oauth-client`).findFirst({
33
+ const client = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-client`).findFirst({
34
34
  filters: { clientId: tokenRecord.clientId }
35
35
  });
36
36
  if (!client) {
@@ -92,18 +92,18 @@ const config = {
92
92
  validator() {
93
93
  }
94
94
  };
95
- const kind$3 = "collectionType";
96
- const collectionName$3 = "mcp_oauth_clients";
97
- const info$3 = {
95
+ const kind$2 = "collectionType";
96
+ const collectionName$2 = "mcp_oauth_clients";
97
+ const info$2 = {
98
98
  singularName: "mcp-oauth-client",
99
99
  pluralName: "mcp-oauth-clients",
100
100
  displayName: "MCP OAuth Client",
101
101
  description: "OAuth 2.0 clients for MCP authentication"
102
102
  };
103
- const options$3 = {
103
+ const options$2 = {
104
104
  draftAndPublish: false
105
105
  };
106
- const pluginOptions$3 = {
106
+ const pluginOptions$2 = {
107
107
  "content-manager": {
108
108
  visible: true
109
109
  },
@@ -111,7 +111,7 @@ const pluginOptions$3 = {
111
111
  visible: true
112
112
  }
113
113
  };
114
- const attributes$3 = {
114
+ const attributes$2 = {
115
115
  name: {
116
116
  type: "string",
117
117
  required: true
@@ -131,7 +131,7 @@ const attributes$3 = {
131
131
  required: true
132
132
  },
133
133
  strapiApiToken: {
134
- type: "string",
134
+ type: "text",
135
135
  required: true,
136
136
  "private": true
137
137
  },
@@ -144,25 +144,25 @@ const attributes$3 = {
144
144
  }
145
145
  };
146
146
  const mcpOauthClient = {
147
- kind: kind$3,
148
- collectionName: collectionName$3,
149
- info: info$3,
150
- options: options$3,
151
- pluginOptions: pluginOptions$3,
152
- attributes: attributes$3
147
+ kind: kind$2,
148
+ collectionName: collectionName$2,
149
+ info: info$2,
150
+ options: options$2,
151
+ pluginOptions: pluginOptions$2,
152
+ attributes: attributes$2
153
153
  };
154
- const kind$2 = "collectionType";
155
- const collectionName$2 = "mcp_oauth_codes";
156
- const info$2 = {
154
+ const kind$1 = "collectionType";
155
+ const collectionName$1 = "mcp_oauth_codes";
156
+ const info$1 = {
157
157
  singularName: "mcp-oauth-code",
158
158
  pluralName: "mcp-oauth-codes",
159
159
  displayName: "MCP OAuth Code",
160
160
  description: "OAuth 2.0 authorization codes"
161
161
  };
162
- const options$2 = {
162
+ const options$1 = {
163
163
  draftAndPublish: false
164
164
  };
165
- const pluginOptions$2 = {
165
+ const pluginOptions$1 = {
166
166
  "content-manager": {
167
167
  visible: false
168
168
  },
@@ -170,7 +170,7 @@ const pluginOptions$2 = {
170
170
  visible: false
171
171
  }
172
172
  };
173
- const attributes$2 = {
173
+ const attributes$1 = {
174
174
  code: {
175
175
  type: "string",
176
176
  required: true,
@@ -200,25 +200,25 @@ const attributes$2 = {
200
200
  }
201
201
  };
202
202
  const mcpOauthCode = {
203
- kind: kind$2,
204
- collectionName: collectionName$2,
205
- info: info$2,
206
- options: options$2,
207
- pluginOptions: pluginOptions$2,
208
- attributes: attributes$2
203
+ kind: kind$1,
204
+ collectionName: collectionName$1,
205
+ info: info$1,
206
+ options: options$1,
207
+ pluginOptions: pluginOptions$1,
208
+ attributes: attributes$1
209
209
  };
210
- const kind$1 = "collectionType";
211
- const collectionName$1 = "mcp_oauth_tokens";
212
- const info$1 = {
210
+ const kind = "collectionType";
211
+ const collectionName = "mcp_oauth_tokens";
212
+ const info = {
213
213
  singularName: "mcp-oauth-token",
214
214
  pluralName: "mcp-oauth-tokens",
215
215
  displayName: "MCP OAuth Token",
216
216
  description: "OAuth 2.0 access and refresh tokens"
217
217
  };
218
- const options$1 = {
218
+ const options = {
219
219
  draftAndPublish: false
220
220
  };
221
- const pluginOptions$1 = {
221
+ const pluginOptions = {
222
222
  "content-manager": {
223
223
  visible: false
224
224
  },
@@ -226,7 +226,7 @@ const pluginOptions$1 = {
226
226
  visible: false
227
227
  }
228
228
  };
229
- const attributes$1 = {
229
+ const attributes = {
230
230
  accessToken: {
231
231
  type: "string",
232
232
  required: true,
@@ -257,55 +257,6 @@ const attributes$1 = {
257
257
  }
258
258
  };
259
259
  const mcpOauthToken = {
260
- kind: kind$1,
261
- collectionName: collectionName$1,
262
- info: info$1,
263
- options: options$1,
264
- pluginOptions: pluginOptions$1,
265
- attributes: attributes$1
266
- };
267
- const kind = "collectionType";
268
- const collectionName = "mcp_endpoints";
269
- const info = {
270
- singularName: "mcp-endpoint",
271
- pluralName: "mcp-endpoints",
272
- displayName: "MCP Endpoint",
273
- description: "Registered MCP endpoints protected by OAuth"
274
- };
275
- const options = {
276
- draftAndPublish: false
277
- };
278
- const pluginOptions = {
279
- "content-manager": {
280
- visible: true
281
- },
282
- "content-type-builder": {
283
- visible: true
284
- }
285
- };
286
- const attributes = {
287
- name: {
288
- type: "string",
289
- required: true
290
- },
291
- pluginId: {
292
- type: "string",
293
- required: true
294
- },
295
- path: {
296
- type: "string",
297
- required: true,
298
- unique: true
299
- },
300
- description: {
301
- type: "text"
302
- },
303
- active: {
304
- type: "boolean",
305
- "default": true
306
- }
307
- };
308
- const mcpEndpoint = {
309
260
  kind,
310
261
  collectionName,
311
262
  info,
@@ -316,10 +267,9 @@ const mcpEndpoint = {
316
267
  const contentTypes = {
317
268
  "mcp-oauth-client": { schema: mcpOauthClient },
318
269
  "mcp-oauth-code": { schema: mcpOauthCode },
319
- "mcp-oauth-token": { schema: mcpOauthToken },
320
- "mcp-endpoint": { schema: mcpEndpoint }
270
+ "mcp-oauth-token": { schema: mcpOauthToken }
321
271
  };
322
- const PLUGIN_UID$2 = `plugin::${PLUGIN_ID}`;
272
+ const PLUGIN_UID$1 = `plugin::${PLUGIN_ID}`;
323
273
  function getBaseUrl(ctx, strapi) {
324
274
  const forwardedProto = ctx.request.headers["x-forwarded-proto"] || ctx.protocol;
325
275
  const forwardedHost = ctx.request.headers["x-forwarded-host"] || ctx.request.headers["host"];
@@ -356,17 +306,15 @@ const oauthController = ({ strapi }) => ({
356
306
  /**
357
307
  * OAuth 2.0 Protected Resource Metadata (RFC 9728)
358
308
  * GET /.well-known/oauth-protected-resource
309
+ *
310
+ * Convention-based protection: all /api/{plugin}/mcp endpoints are protected.
359
311
  */
360
312
  async protectedResource(ctx) {
361
313
  const baseUrl = getBaseUrl(ctx, strapi);
362
314
  const pluginPath = `/api/${PLUGIN_ID}`;
363
315
  const authServer = `${baseUrl}${pluginPath}`;
364
- const endpoints = await strapi.documents(`${PLUGIN_UID$2}.mcp-endpoint`).findMany({
365
- filters: { active: true }
366
- });
367
- const resources = endpoints.map((e) => `${baseUrl}${e.path}`);
368
316
  ctx.body = {
369
- resource: resources.length === 1 ? resources[0] : resources,
317
+ resource: `${baseUrl}/api`,
370
318
  authorization_servers: [authServer],
371
319
  bearer_methods_supported: ["header"]
372
320
  };
@@ -392,7 +340,7 @@ const oauthController = ({ strapi }) => ({
392
340
  ctx.body = { error: "unsupported_response_type", error_description: "Only code response type is supported" };
393
341
  return;
394
342
  }
395
- const client = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-client`).findFirst({
343
+ const client = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-client`).findFirst({
396
344
  filters: { clientId: client_id, active: true }
397
345
  });
398
346
  if (!client) {
@@ -421,7 +369,7 @@ const oauthController = ({ strapi }) => ({
421
369
  }
422
370
  const code = randomBytes(32).toString("hex");
423
371
  const expiresAt = new Date(Date.now() + 10 * 60 * 1e3);
424
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-code`).create({
372
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).create({
425
373
  data: {
426
374
  code,
427
375
  clientId: client_id,
@@ -459,7 +407,7 @@ const oauthController = ({ strapi }) => ({
459
407
  ctx.body = { error: "invalid_client", error_description: "Client authentication required" };
460
408
  return;
461
409
  }
462
- const client = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-client`).findFirst({
410
+ const client = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-client`).findFirst({
463
411
  filters: { clientId: authClientId, active: true }
464
412
  });
465
413
  if (!client || authClientSecret && client.clientSecret !== authClientSecret) {
@@ -483,7 +431,7 @@ async function handleAuthorizationCodeGrant(ctx, strapi, client, code, redirect_
483
431
  ctx.body = { error: "invalid_request", error_description: "code is required" };
484
432
  return;
485
433
  }
486
- const authCode = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-code`).findFirst({
434
+ const authCode = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).findFirst({
487
435
  filters: { code, clientId: client.clientId, used: false }
488
436
  });
489
437
  if (!authCode) {
@@ -501,7 +449,7 @@ async function handleAuthorizationCodeGrant(ctx, strapi, client, code, redirect_
501
449
  ctx.body = { error: "invalid_grant", error_description: "redirect_uri mismatch" };
502
450
  return;
503
451
  }
504
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-code`).update({
452
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).update({
505
453
  documentId: authCode.documentId,
506
454
  data: { used: true }
507
455
  });
@@ -509,7 +457,7 @@ async function handleAuthorizationCodeGrant(ctx, strapi, client, code, redirect_
509
457
  const refreshToken = randomBytes(32).toString("hex");
510
458
  const expiresIn = 3600;
511
459
  const refreshExpiresIn = 30 * 24 * 3600;
512
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).create({
460
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).create({
513
461
  data: {
514
462
  accessToken,
515
463
  refreshToken,
@@ -532,7 +480,7 @@ async function handleRefreshTokenGrant(ctx, strapi, client, refreshToken) {
532
480
  ctx.body = { error: "invalid_request", error_description: "refresh_token is required" };
533
481
  return;
534
482
  }
535
- const token = await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).findFirst({
483
+ const token = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findFirst({
536
484
  filters: { refreshToken, clientId: client.clientId, revoked: false }
537
485
  });
538
486
  if (!token) {
@@ -545,7 +493,7 @@ async function handleRefreshTokenGrant(ctx, strapi, client, refreshToken) {
545
493
  ctx.body = { error: "invalid_grant", error_description: "Refresh token expired" };
546
494
  return;
547
495
  }
548
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).update({
496
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).update({
549
497
  documentId: token.documentId,
550
498
  data: { revoked: true }
551
499
  });
@@ -553,7 +501,7 @@ async function handleRefreshTokenGrant(ctx, strapi, client, refreshToken) {
553
501
  const newRefreshToken = randomBytes(32).toString("hex");
554
502
  const expiresIn = 3600;
555
503
  const refreshExpiresIn = 30 * 24 * 3600;
556
- await strapi.documents(`${PLUGIN_UID$2}.mcp-oauth-token`).create({
504
+ await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).create({
557
505
  data: {
558
506
  accessToken: newAccessToken,
559
507
  refreshToken: newRefreshToken,
@@ -630,14 +578,14 @@ const routes = {
630
578
  routes: [...admin]
631
579
  }
632
580
  };
633
- const PLUGIN_UID$1 = `plugin::${PLUGIN_ID}`;
581
+ const PLUGIN_UID = `plugin::${PLUGIN_ID}`;
634
582
  const oauthService = ({ strapi }) => ({
635
583
  /**
636
584
  * Validate an OAuth access token
637
585
  */
638
586
  async validateToken(accessToken) {
639
587
  try {
640
- const tokenRecord = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findFirst({
588
+ const tokenRecord = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).findFirst({
641
589
  filters: { accessToken, revoked: false }
642
590
  });
643
591
  if (!tokenRecord) {
@@ -646,7 +594,7 @@ const oauthService = ({ strapi }) => ({
646
594
  if (new Date(tokenRecord.expiresAt) < /* @__PURE__ */ new Date()) {
647
595
  return { valid: false, error: "Token expired" };
648
596
  }
649
- const client = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-client`).findFirst({
597
+ const client = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-client`).findFirst({
650
598
  filters: { clientId: tokenRecord.clientId }
651
599
  });
652
600
  if (!client) {
@@ -666,11 +614,11 @@ const oauthService = ({ strapi }) => ({
666
614
  * Revoke all tokens for a client
667
615
  */
668
616
  async revokeClientTokens(clientId) {
669
- const tokens = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findMany({
617
+ const tokens = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).findMany({
670
618
  filters: { clientId, revoked: false }
671
619
  });
672
620
  for (const token of tokens) {
673
- await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).update({
621
+ await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).update({
674
622
  documentId: token.documentId,
675
623
  data: { revoked: true }
676
624
  });
@@ -682,19 +630,19 @@ const oauthService = ({ strapi }) => ({
682
630
  */
683
631
  async cleanupExpired() {
684
632
  const now = (/* @__PURE__ */ new Date()).toISOString();
685
- const expiredCodes = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).findMany({
633
+ const expiredCodes = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-code`).findMany({
686
634
  filters: { expiresAt: { $lt: now } }
687
635
  });
688
636
  for (const code of expiredCodes) {
689
- await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-code`).delete({
637
+ await strapi.documents(`${PLUGIN_UID}.mcp-oauth-code`).delete({
690
638
  documentId: code.documentId
691
639
  });
692
640
  }
693
- const expiredTokens = await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).findMany({
641
+ const expiredTokens = await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).findMany({
694
642
  filters: { refreshExpiresAt: { $lt: now } }
695
643
  });
696
644
  for (const token of expiredTokens) {
697
- await strapi.documents(`${PLUGIN_UID$1}.mcp-oauth-token`).delete({
645
+ await strapi.documents(`${PLUGIN_UID}.mcp-oauth-token`).delete({
698
646
  documentId: token.documentId
699
647
  });
700
648
  }
@@ -704,80 +652,8 @@ const oauthService = ({ strapi }) => ({
704
652
  };
705
653
  }
706
654
  });
707
- const PLUGIN_UID = `plugin::${PLUGIN_ID}`;
708
- const endpointService = ({ strapi }) => ({
709
- /**
710
- * Register an MCP endpoint for OAuth protection
711
- */
712
- async register(endpoint) {
713
- const existing = await strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findFirst({
714
- filters: { path: endpoint.path }
715
- });
716
- if (existing) {
717
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).update({
718
- documentId: existing.documentId,
719
- data: {
720
- name: endpoint.name,
721
- pluginId: endpoint.pluginId,
722
- description: endpoint.description,
723
- active: true
724
- }
725
- });
726
- }
727
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).create({
728
- data: {
729
- name: endpoint.name,
730
- pluginId: endpoint.pluginId,
731
- path: endpoint.path,
732
- description: endpoint.description,
733
- active: true
734
- }
735
- });
736
- },
737
- /**
738
- * Unregister an MCP endpoint
739
- */
740
- async unregister(path) {
741
- const endpoint = await strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findFirst({
742
- filters: { path }
743
- });
744
- if (!endpoint) {
745
- return false;
746
- }
747
- await strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).update({
748
- documentId: endpoint.documentId,
749
- data: { active: false }
750
- });
751
- return true;
752
- },
753
- /**
754
- * Get all registered endpoints
755
- */
756
- async getAll(activeOnly = true) {
757
- const filters = activeOnly ? { active: true } : {};
758
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findMany({ filters });
759
- },
760
- /**
761
- * Get endpoints for a specific plugin
762
- */
763
- async getByPlugin(pluginId, activeOnly = true) {
764
- const filters = { pluginId };
765
- if (activeOnly) {
766
- filters.active = true;
767
- }
768
- return strapi.documents(`${PLUGIN_UID}.mcp-endpoint`).findMany({ filters });
769
- },
770
- /**
771
- * Check if a path is a protected endpoint
772
- */
773
- async isProtected(path) {
774
- const endpoints = await this.getAll(true);
775
- return endpoints.some((e) => path.includes(e.path));
776
- }
777
- });
778
655
  const services = {
779
- oauth: oauthService,
780
- endpoint: endpointService
656
+ oauth: oauthService
781
657
  };
782
658
  const index = {
783
659
  register,
@@ -159,50 +159,5 @@ declare const _default: {
159
159
  };
160
160
  };
161
161
  };
162
- 'mcp-endpoint': {
163
- schema: {
164
- kind: string;
165
- collectionName: string;
166
- info: {
167
- singularName: string;
168
- pluralName: string;
169
- displayName: string;
170
- description: string;
171
- };
172
- options: {
173
- draftAndPublish: boolean;
174
- };
175
- pluginOptions: {
176
- "content-manager": {
177
- visible: boolean;
178
- };
179
- "content-type-builder": {
180
- visible: boolean;
181
- };
182
- };
183
- attributes: {
184
- name: {
185
- type: string;
186
- required: boolean;
187
- };
188
- pluginId: {
189
- type: string;
190
- required: boolean;
191
- };
192
- path: {
193
- type: string;
194
- required: boolean;
195
- unique: boolean;
196
- };
197
- description: {
198
- type: string;
199
- };
200
- active: {
201
- type: string;
202
- default: boolean;
203
- };
204
- };
205
- };
206
- };
207
162
  };
208
163
  export default _default;
@@ -15,6 +15,8 @@ declare const oauthController: ({ strapi }: {
15
15
  /**
16
16
  * OAuth 2.0 Protected Resource Metadata (RFC 9728)
17
17
  * GET /.well-known/oauth-protected-resource
18
+ *
19
+ * Convention-based protection: all /api/{plugin}/mcp endpoints are protected.
18
20
  */
19
21
  protectedResource(ctx: any): Promise<void>;
20
22
  /**
@@ -51,15 +51,6 @@ declare const _default: {
51
51
  codes: number;
52
52
  }>;
53
53
  };
54
- endpoint: ({ strapi }: {
55
- strapi: import("@strapi/types/dist/core").Strapi;
56
- }) => {
57
- register(endpoint: import("./services/endpoint").EndpointRegistration): Promise<any>;
58
- unregister(path: string): Promise<boolean>;
59
- getAll(activeOnly?: boolean): Promise<any[]>;
60
- getByPlugin(pluginId: string, activeOnly?: boolean): Promise<any[]>;
61
- isProtected(path: string): Promise<boolean>;
62
- };
63
54
  };
64
55
  contentTypes: {
65
56
  'mcp-oauth-client': {
@@ -222,51 +213,6 @@ declare const _default: {
222
213
  };
223
214
  };
224
215
  };
225
- 'mcp-endpoint': {
226
- schema: {
227
- kind: string;
228
- collectionName: string;
229
- info: {
230
- singularName: string;
231
- pluralName: string;
232
- displayName: string;
233
- description: string;
234
- };
235
- options: {
236
- draftAndPublish: boolean;
237
- };
238
- pluginOptions: {
239
- "content-manager": {
240
- visible: boolean;
241
- };
242
- "content-type-builder": {
243
- visible: boolean;
244
- };
245
- };
246
- attributes: {
247
- name: {
248
- type: string;
249
- required: boolean;
250
- };
251
- pluginId: {
252
- type: string;
253
- required: boolean;
254
- };
255
- path: {
256
- type: string;
257
- required: boolean;
258
- unique: boolean;
259
- };
260
- description: {
261
- type: string;
262
- };
263
- active: {
264
- type: string;
265
- default: boolean;
266
- };
267
- };
268
- };
269
- };
270
216
  };
271
217
  policies: {};
272
218
  middlewares: {
@@ -9,14 +9,5 @@ declare const _default: {
9
9
  codes: number;
10
10
  }>;
11
11
  };
12
- endpoint: ({ strapi }: {
13
- strapi: import("@strapi/types/dist/core").Strapi;
14
- }) => {
15
- register(endpoint: import("./endpoint").EndpointRegistration): Promise<any>;
16
- unregister(path: string): Promise<boolean>;
17
- getAll(activeOnly?: boolean): Promise<any[]>;
18
- getByPlugin(pluginId: string, activeOnly?: boolean): Promise<any[]>;
19
- isProtected(path: string): Promise<boolean>;
20
- };
21
12
  };
22
13
  export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-oauth-mcp-manager",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Centralized OAuth 2.0 authentication manager for Strapi MCP plugins. Supports ChatGPT, Claude, and custom OAuth clients.",
5
5
  "keywords": [
6
6
  "strapi",
@@ -1,37 +0,0 @@
1
- /**
2
- * Endpoint Registration Service
3
- *
4
- * Allows MCP plugins to register their endpoints for OAuth protection.
5
- */
6
- import type { Core } from '@strapi/strapi';
7
- export interface EndpointRegistration {
8
- name: string;
9
- pluginId: string;
10
- path: string;
11
- description?: string;
12
- }
13
- declare const endpointService: ({ strapi }: {
14
- strapi: Core.Strapi;
15
- }) => {
16
- /**
17
- * Register an MCP endpoint for OAuth protection
18
- */
19
- register(endpoint: EndpointRegistration): Promise<any>;
20
- /**
21
- * Unregister an MCP endpoint
22
- */
23
- unregister(path: string): Promise<boolean>;
24
- /**
25
- * Get all registered endpoints
26
- */
27
- getAll(activeOnly?: boolean): Promise<any[]>;
28
- /**
29
- * Get endpoints for a specific plugin
30
- */
31
- getByPlugin(pluginId: string, activeOnly?: boolean): Promise<any[]>;
32
- /**
33
- * Check if a path is a protected endpoint
34
- */
35
- isProtected(path: string): Promise<boolean>;
36
- };
37
- export default endpointService;