strapi-oauth-mcp-manager 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 (37) hide show
  1. package/README.md +143 -0
  2. package/dist/_chunks/App-CjW3NftW.mjs +23 -0
  3. package/dist/_chunks/App-DsMhfKkM.js +23 -0
  4. package/dist/_chunks/en-B4KWt_jN.js +4 -0
  5. package/dist/_chunks/en-Byx4XI2L.mjs +4 -0
  6. package/dist/_chunks/index-B2ShbPnj.js +65 -0
  7. package/dist/_chunks/index-DzBaU9Fw.mjs +66 -0
  8. package/dist/admin/index.js +3 -0
  9. package/dist/admin/index.mjs +4 -0
  10. package/dist/admin/src/components/Initializer.d.ts +5 -0
  11. package/dist/admin/src/components/PluginIcon.d.ts +2 -0
  12. package/dist/admin/src/index.d.ts +10 -0
  13. package/dist/admin/src/pages/App.d.ts +2 -0
  14. package/dist/admin/src/pages/HomePage.d.ts +2 -0
  15. package/dist/admin/src/pluginId.d.ts +1 -0
  16. package/dist/admin/src/utils/getTranslation.d.ts +2 -0
  17. package/dist/server/index.js +800 -0
  18. package/dist/server/index.mjs +801 -0
  19. package/dist/server/src/bootstrap.d.ts +5 -0
  20. package/dist/server/src/config/index.d.ts +5 -0
  21. package/dist/server/src/content-types/index.d.ts +208 -0
  22. package/dist/server/src/controllers/index.d.ts +11 -0
  23. package/dist/server/src/controllers/oauth.d.ts +31 -0
  24. package/dist/server/src/destroy.d.ts +5 -0
  25. package/dist/server/src/index.d.ts +278 -0
  26. package/dist/server/src/middlewares/index.d.ts +6 -0
  27. package/dist/server/src/middlewares/mcp-oauth.d.ts +19 -0
  28. package/dist/server/src/pluginId.d.ts +1 -0
  29. package/dist/server/src/policies/index.d.ts +2 -0
  30. package/dist/server/src/register.d.ts +5 -0
  31. package/dist/server/src/routes/admin/index.d.ts +2 -0
  32. package/dist/server/src/routes/content-api/index.d.ts +10 -0
  33. package/dist/server/src/routes/index.d.ts +19 -0
  34. package/dist/server/src/services/endpoint.d.ts +37 -0
  35. package/dist/server/src/services/index.d.ts +22 -0
  36. package/dist/server/src/services/oauth.d.ts +32 -0
  37. package/package.json +88 -0
package/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # Strapi OAuth MCP Manager
2
+
3
+ Centralized OAuth 2.0 authentication manager for Strapi MCP (Model Context Protocol) plugins. Enables ChatGPT, Claude, and other AI assistants to securely authenticate with your Strapi MCP endpoints.
4
+
5
+ ## Features
6
+
7
+ - **Centralized OAuth 2.0** - Single authentication layer for all MCP plugins
8
+ - **RFC Compliant** - Implements RFC 6749, RFC 8414, and RFC 9728
9
+ - **Dual Authentication** - Supports both OAuth tokens and Strapi API tokens
10
+ - **ChatGPT Ready** - Works with ChatGPT's MCP integration out of the box
11
+ - **Claude Compatible** - Supports direct API token authentication
12
+ - **Admin Management** - Manage OAuth clients through Strapi admin panel
13
+ - **Wildcard Redirects** - Supports wildcard patterns in redirect URIs
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install strapi-oauth-mcp-manager
19
+ ```
20
+
21
+ ## Configuration
22
+
23
+ Add the plugin to your `config/plugins.ts`:
24
+
25
+ ```typescript
26
+ export default () => ({
27
+ 'strapi-oauth-mcp-manager': {
28
+ enabled: true,
29
+ },
30
+ // Your other MCP plugins...
31
+ 'yt-transcript-strapi-plugin': {
32
+ enabled: true,
33
+ },
34
+ });
35
+ ```
36
+
37
+ ## How It Works
38
+
39
+ ### For MCP Plugin Developers
40
+
41
+ MCP plugins register with the OAuth manager to gain authentication support:
42
+
43
+ ```typescript
44
+ // In your plugin's bootstrap.ts
45
+ const oauthPlugin = strapi.plugin('strapi-oauth-mcp-manager');
46
+
47
+ if (oauthPlugin) {
48
+ await oauthPlugin.service('endpoint').register({
49
+ name: 'My MCP Plugin',
50
+ pluginId: 'my-mcp-plugin',
51
+ path: '/api/my-mcp-plugin/mcp',
52
+ description: 'MCP endpoint for my tools',
53
+ });
54
+ }
55
+ ```
56
+
57
+ ### For Users
58
+
59
+ 1. **Create an OAuth Client** in Strapi Admin:
60
+ - Go to Content Manager > MCP OAuth Client
61
+ - Add client name, ID, secret, and redirect URIs
62
+ - Link to a Strapi API token
63
+
64
+ 2. **Configure ChatGPT**:
65
+ - MCP Server URL: `https://your-domain/api/your-mcp-plugin/mcp`
66
+ - OAuth Client ID: From Strapi admin
67
+ - OAuth Client Secret: From Strapi admin
68
+
69
+ ## OAuth Endpoints
70
+
71
+ | Endpoint | URL |
72
+ |----------|-----|
73
+ | Authorization | `/api/strapi-oauth-mcp-manager/oauth/authorize` |
74
+ | Token | `/api/strapi-oauth-mcp-manager/oauth/token` |
75
+ | Discovery (RFC 8414) | `/api/strapi-oauth-mcp-manager/.well-known/oauth-authorization-server` |
76
+ | Protected Resource (RFC 9728) | `/api/strapi-oauth-mcp-manager/.well-known/oauth-protected-resource` |
77
+
78
+ ## Content Types
79
+
80
+ The plugin creates these content types:
81
+
82
+ | Content Type | Purpose |
83
+ |--------------|---------|
84
+ | `mcp-oauth-client` | OAuth client configurations |
85
+ | `mcp-oauth-code` | Authorization codes (temporary) |
86
+ | `mcp-oauth-token` | Access and refresh tokens |
87
+ | `mcp-endpoint` | Registered MCP endpoints |
88
+
89
+ ## Creating an OAuth Client for ChatGPT
90
+
91
+ In Strapi Admin > Content Manager > MCP OAuth Client:
92
+
93
+ ```json
94
+ {
95
+ "name": "ChatGPT",
96
+ "clientId": "chatgpt",
97
+ "clientSecret": "your-secure-secret",
98
+ "redirectUris": ["https://chatgpt.com/connector_platform_oauth_redirect"],
99
+ "strapiApiToken": "your-strapi-api-token",
100
+ "active": true
101
+ }
102
+ ```
103
+
104
+ ## Authentication Flow
105
+
106
+ ```
107
+ 1. Client hits MCP endpoint without auth
108
+ 2. Returns 401 with WWW-Authenticate header
109
+ 3. Client discovers OAuth endpoints via .well-known
110
+ 4. Client completes OAuth authorization code flow
111
+ 5. Client receives access token
112
+ 6. Client makes MCP requests with Bearer token
113
+ 7. OAuth manager validates and forwards to MCP plugin
114
+ ```
115
+
116
+ ## Claude Desktop (API Token)
117
+
118
+ For Claude Desktop, use direct Strapi API tokens:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "your-mcp": {
124
+ "command": "npx",
125
+ "args": [
126
+ "mcp-remote",
127
+ "https://your-domain/api/your-mcp-plugin/mcp",
128
+ "--header",
129
+ "Authorization: Bearer YOUR_STRAPI_API_TOKEN"
130
+ ]
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Requirements
137
+
138
+ - Strapi v5.x
139
+ - Node.js >= 18
140
+
141
+ ## License
142
+
143
+ MIT
@@ -0,0 +1,23 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Page } from "@strapi/strapi/admin";
3
+ import { Routes, Route } from "react-router-dom";
4
+ import { Main } from "@strapi/design-system";
5
+ import { useIntl } from "react-intl";
6
+ import { P as PLUGIN_ID } from "./index-DzBaU9Fw.mjs";
7
+ const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
8
+ const HomePage = () => {
9
+ const { formatMessage } = useIntl();
10
+ return /* @__PURE__ */ jsx(Main, { children: /* @__PURE__ */ jsxs("h1", { children: [
11
+ "Welcome to ",
12
+ formatMessage({ id: getTranslation("plugin.name") })
13
+ ] }) });
14
+ };
15
+ const App = () => {
16
+ return /* @__PURE__ */ jsxs(Routes, { children: [
17
+ /* @__PURE__ */ jsx(Route, { index: true, element: /* @__PURE__ */ jsx(HomePage, {}) }),
18
+ /* @__PURE__ */ jsx(Route, { path: "*", element: /* @__PURE__ */ jsx(Page.Error, {}) })
19
+ ] });
20
+ };
21
+ export {
22
+ App
23
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const admin = require("@strapi/strapi/admin");
5
+ const reactRouterDom = require("react-router-dom");
6
+ const designSystem = require("@strapi/design-system");
7
+ const reactIntl = require("react-intl");
8
+ const index = require("./index-B2ShbPnj.js");
9
+ const getTranslation = (id) => `${index.PLUGIN_ID}.${id}`;
10
+ const HomePage = () => {
11
+ const { formatMessage } = reactIntl.useIntl();
12
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Main, { children: /* @__PURE__ */ jsxRuntime.jsxs("h1", { children: [
13
+ "Welcome to ",
14
+ formatMessage({ id: getTranslation("plugin.name") })
15
+ ] }) });
16
+ };
17
+ const App = () => {
18
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
19
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, {}) }),
20
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
21
+ ] });
22
+ };
23
+ exports.App = App;
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const en = {};
4
+ exports.default = en;
@@ -0,0 +1,4 @@
1
+ const en = {};
2
+ export {
3
+ en as default
4
+ };
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ const react = require("react");
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const icons = require("@strapi/icons");
5
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
6
+ const v = glob[path];
7
+ if (v) {
8
+ return typeof v === "function" ? v() : Promise.resolve(v);
9
+ }
10
+ return new Promise((_, reject) => {
11
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
12
+ reject.bind(
13
+ null,
14
+ new Error(
15
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
16
+ )
17
+ )
18
+ );
19
+ });
20
+ };
21
+ const PLUGIN_ID = "strapi-oauth-mcp-manager";
22
+ const Initializer = ({ setPlugin }) => {
23
+ const ref = react.useRef(setPlugin);
24
+ react.useEffect(() => {
25
+ ref.current(PLUGIN_ID);
26
+ }, []);
27
+ return null;
28
+ };
29
+ const PluginIcon = () => /* @__PURE__ */ jsxRuntime.jsx(icons.PuzzlePiece, {});
30
+ const index = {
31
+ register(app) {
32
+ app.addMenuLink({
33
+ to: `plugins/${PLUGIN_ID}`,
34
+ icon: PluginIcon,
35
+ intlLabel: {
36
+ id: `${PLUGIN_ID}.plugin.name`,
37
+ defaultMessage: PLUGIN_ID
38
+ },
39
+ Component: async () => {
40
+ const { App } = await Promise.resolve().then(() => require("./App-DsMhfKkM.js"));
41
+ return App;
42
+ }
43
+ });
44
+ app.registerPlugin({
45
+ id: PLUGIN_ID,
46
+ initializer: Initializer,
47
+ isReady: false,
48
+ name: PLUGIN_ID
49
+ });
50
+ },
51
+ async registerTrads({ locales }) {
52
+ return Promise.all(
53
+ locales.map(async (locale) => {
54
+ try {
55
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-B4KWt_jN.js")) }), `./translations/${locale}.json`, 3);
56
+ return { data, locale };
57
+ } catch {
58
+ return { data: {}, locale };
59
+ }
60
+ })
61
+ );
62
+ }
63
+ };
64
+ exports.PLUGIN_ID = PLUGIN_ID;
65
+ exports.index = index;
@@ -0,0 +1,66 @@
1
+ import { useRef, useEffect } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { PuzzlePiece } from "@strapi/icons";
4
+ const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
5
+ const v = glob[path];
6
+ if (v) {
7
+ return typeof v === "function" ? v() : Promise.resolve(v);
8
+ }
9
+ return new Promise((_, reject) => {
10
+ (typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
11
+ reject.bind(
12
+ null,
13
+ new Error(
14
+ "Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
15
+ )
16
+ )
17
+ );
18
+ });
19
+ };
20
+ const PLUGIN_ID = "strapi-oauth-mcp-manager";
21
+ const Initializer = ({ setPlugin }) => {
22
+ const ref = useRef(setPlugin);
23
+ useEffect(() => {
24
+ ref.current(PLUGIN_ID);
25
+ }, []);
26
+ return null;
27
+ };
28
+ const PluginIcon = () => /* @__PURE__ */ jsx(PuzzlePiece, {});
29
+ const index = {
30
+ register(app) {
31
+ app.addMenuLink({
32
+ to: `plugins/${PLUGIN_ID}`,
33
+ icon: PluginIcon,
34
+ intlLabel: {
35
+ id: `${PLUGIN_ID}.plugin.name`,
36
+ defaultMessage: PLUGIN_ID
37
+ },
38
+ Component: async () => {
39
+ const { App } = await import("./App-CjW3NftW.mjs");
40
+ return App;
41
+ }
42
+ });
43
+ app.registerPlugin({
44
+ id: PLUGIN_ID,
45
+ initializer: Initializer,
46
+ isReady: false,
47
+ name: PLUGIN_ID
48
+ });
49
+ },
50
+ async registerTrads({ locales }) {
51
+ return Promise.all(
52
+ locales.map(async (locale) => {
53
+ try {
54
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-Byx4XI2L.mjs") }), `./translations/${locale}.json`, 3);
55
+ return { data, locale };
56
+ } catch {
57
+ return { data: {}, locale };
58
+ }
59
+ })
60
+ );
61
+ }
62
+ };
63
+ export {
64
+ PLUGIN_ID as P,
65
+ index as i
66
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ const index = require("../_chunks/index-B2ShbPnj.js");
3
+ module.exports = index.index;
@@ -0,0 +1,4 @@
1
+ import { i } from "../_chunks/index-DzBaU9Fw.mjs";
2
+ export {
3
+ i as default
4
+ };
@@ -0,0 +1,5 @@
1
+ type InitializerProps = {
2
+ setPlugin: (id: string) => void;
3
+ };
4
+ declare const Initializer: ({ setPlugin }: InitializerProps) => null;
5
+ export { Initializer };
@@ -0,0 +1,2 @@
1
+ declare const PluginIcon: () => import("react/jsx-runtime").JSX.Element;
2
+ export { PluginIcon };
@@ -0,0 +1,10 @@
1
+ declare const _default: {
2
+ register(app: any): void;
3
+ registerTrads({ locales }: {
4
+ locales: string[];
5
+ }): Promise<{
6
+ data: any;
7
+ locale: string;
8
+ }[]>;
9
+ };
10
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const App: () => import("react/jsx-runtime").JSX.Element;
2
+ export { App };
@@ -0,0 +1,2 @@
1
+ declare const HomePage: () => import("react/jsx-runtime").JSX.Element;
2
+ export { HomePage };
@@ -0,0 +1 @@
1
+ export declare const PLUGIN_ID = "strapi-oauth-mcp-manager";
@@ -0,0 +1,2 @@
1
+ declare const getTranslation: (id: string) => string;
2
+ export { getTranslation };