strapi-mcp-server 0.1.1

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 (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +415 -0
  3. package/admin/src/components/PageHeader.tsx +33 -0
  4. package/admin/src/components/Sidebar.tsx +138 -0
  5. package/admin/src/index.tsx +54 -0
  6. package/admin/src/lib/api.ts +27 -0
  7. package/admin/src/lib/applyQuery.ts +152 -0
  8. package/admin/src/pages/App.tsx +126 -0
  9. package/admin/src/pages/AuditLog.tsx +386 -0
  10. package/admin/src/pages/Clients.tsx +465 -0
  11. package/admin/src/pages/EditClient.tsx +248 -0
  12. package/admin/src/pages/HomePage.tsx +378 -0
  13. package/admin/src/pages/NewClient.tsx +244 -0
  14. package/admin/src/pages/Settings.tsx +514 -0
  15. package/admin/src/pages/SsoBridge.tsx +96 -0
  16. package/admin/src/pages/Tools.tsx +68 -0
  17. package/admin/src/pluginId.ts +1 -0
  18. package/admin/src/translations/en.json +8 -0
  19. package/package.json +105 -0
  20. package/server/src/bootstrap.ts +118 -0
  21. package/server/src/config/index.ts +290 -0
  22. package/server/src/content-types/audit-log/index.ts +3 -0
  23. package/server/src/content-types/audit-log/schema.json +32 -0
  24. package/server/src/content-types/index.ts +19 -0
  25. package/server/src/content-types/oauth-auth-code/index.ts +3 -0
  26. package/server/src/content-types/oauth-auth-code/schema.json +31 -0
  27. package/server/src/content-types/oauth-client/index.ts +3 -0
  28. package/server/src/content-types/oauth-client/schema.json +33 -0
  29. package/server/src/content-types/oauth-consent/index.ts +3 -0
  30. package/server/src/content-types/oauth-consent/schema.json +21 -0
  31. package/server/src/content-types/oauth-refresh-token/index.ts +3 -0
  32. package/server/src/content-types/oauth-refresh-token/schema.json +25 -0
  33. package/server/src/content-types/oauth-revocation/index.ts +3 -0
  34. package/server/src/content-types/oauth-revocation/schema.json +18 -0
  35. package/server/src/content-types/oauth-signing-key/index.ts +3 -0
  36. package/server/src/content-types/oauth-signing-key/schema.json +21 -0
  37. package/server/src/controllers/admin/audit.ts +30 -0
  38. package/server/src/controllers/admin/clients.ts +148 -0
  39. package/server/src/controllers/admin/dashboard.ts +28 -0
  40. package/server/src/controllers/admin/index.ts +15 -0
  41. package/server/src/controllers/admin/settings.ts +38 -0
  42. package/server/src/controllers/admin/tools.ts +23 -0
  43. package/server/src/controllers/index.ts +13 -0
  44. package/server/src/controllers/mcp.ts +168 -0
  45. package/server/src/controllers/oauth/authorize.ts +418 -0
  46. package/server/src/controllers/oauth/index.ts +15 -0
  47. package/server/src/controllers/oauth/introspect.ts +45 -0
  48. package/server/src/controllers/oauth/metadata.ts +86 -0
  49. package/server/src/controllers/oauth/mode-guard.ts +22 -0
  50. package/server/src/controllers/oauth/register.ts +109 -0
  51. package/server/src/controllers/oauth/token.ts +206 -0
  52. package/server/src/controllers/proxy.ts +81 -0
  53. package/server/src/destroy.ts +28 -0
  54. package/server/src/index.ts +23 -0
  55. package/server/src/policies/authenticate.ts +81 -0
  56. package/server/src/policies/index.ts +13 -0
  57. package/server/src/policies/origin.ts +50 -0
  58. package/server/src/policies/rateLimit.ts +27 -0
  59. package/server/src/policies/scope.ts +32 -0
  60. package/server/src/register.ts +48 -0
  61. package/server/src/routes/admin.ts +85 -0
  62. package/server/src/routes/index.ts +13 -0
  63. package/server/src/routes/mcp.ts +31 -0
  64. package/server/src/routes/oauth.ts +81 -0
  65. package/server/src/routes/proxy.ts +29 -0
  66. package/server/src/services/audit.ts +158 -0
  67. package/server/src/services/heartbeat.ts +76 -0
  68. package/server/src/services/index.ts +37 -0
  69. package/server/src/services/instance-id.ts +30 -0
  70. package/server/src/services/mcp-server.ts +100 -0
  71. package/server/src/services/oauth/audience.ts +26 -0
  72. package/server/src/services/oauth/auth-codes.ts +78 -0
  73. package/server/src/services/oauth/clients.ts +386 -0
  74. package/server/src/services/oauth/consent.ts +38 -0
  75. package/server/src/services/oauth/errors.ts +32 -0
  76. package/server/src/services/oauth/pkce.ts +34 -0
  77. package/server/src/services/oauth/scopes.ts +42 -0
  78. package/server/src/services/oauth/signing-keys.ts +166 -0
  79. package/server/src/services/oauth/tokens.ts +324 -0
  80. package/server/src/services/permissions.ts +87 -0
  81. package/server/src/services/proxy-client.ts +167 -0
  82. package/server/src/services/rate-limiter.ts +180 -0
  83. package/server/src/services/redis.ts +139 -0
  84. package/server/src/services/session-directory.ts +121 -0
  85. package/server/src/services/session-store.ts +216 -0
  86. package/server/src/services/sso-cookie.ts +146 -0
  87. package/server/src/services/tools/content.ts +284 -0
  88. package/server/src/services/tools/index.ts +23 -0
  89. package/server/src/services/tools/media.ts +170 -0
@@ -0,0 +1,244 @@
1
+ import { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import {
4
+ Box,
5
+ Button,
6
+ Flex,
7
+ Typography,
8
+ TextInput,
9
+ Textarea,
10
+ Checkbox,
11
+ Grid,
12
+ } from '@strapi/design-system';
13
+ import { ArrowLeft } from '@strapi/icons';
14
+ import { useMcpApi } from '../lib/api';
15
+ import { PageHeader } from '../components/PageHeader';
16
+
17
+ const ALL_SCOPES = [
18
+ { id: 'strapi:content:read', label: 'Read content (list types, schemas, entries)' },
19
+ { id: 'strapi:content:write', label: 'Create / update content entries (draft only)' },
20
+ { id: 'strapi:media:read', label: 'List media files' },
21
+ { id: 'strapi:media:write', label: 'Upload media files' },
22
+ ];
23
+
24
+ interface CreatedResponse {
25
+ client: { clientId: string; clientName: string };
26
+ clientSecret?: string;
27
+ }
28
+
29
+ export function NewClient(): JSX.Element {
30
+ const api = useMcpApi();
31
+ const navigate = useNavigate();
32
+
33
+ const [name, setName] = useState('');
34
+ const [redirects, setRedirects] = useState('');
35
+ const [isConfidential, setIsConfidential] = useState(false);
36
+ const [scopes, setScopes] = useState<string[]>(ALL_SCOPES.map((s) => s.id));
37
+ const [error, setError] = useState<string | null>(null);
38
+ const [submitting, setSubmitting] = useState(false);
39
+ const [created, setCreated] = useState<CreatedResponse | null>(null);
40
+
41
+ async function submit(): Promise<void> {
42
+ if (!name.trim() || scopes.length === 0) {
43
+ setError('Name and at least one scope are required.');
44
+ return;
45
+ }
46
+ setSubmitting(true);
47
+ setError(null);
48
+ try {
49
+ // Empty redirect URIs → default to the CLI-friendly loopback URL.
50
+ // Port is ignored at match time, so a CLI on any free port matches.
51
+ const parsedRedirects = redirects
52
+ .split('\n')
53
+ .map((s) => s.trim())
54
+ .filter(Boolean);
55
+ const body = {
56
+ clientName: name.trim(),
57
+ redirectUris: parsedRedirects.length > 0 ? parsedRedirects : ['http://localhost/callback'],
58
+ scopes,
59
+ isConfidential,
60
+ };
61
+ const result = await api.createClient(body);
62
+ setCreated(result);
63
+ } catch (err) {
64
+ setError((err as Error).message);
65
+ } finally {
66
+ setSubmitting(false);
67
+ }
68
+ }
69
+
70
+ if (created) {
71
+ return (
72
+ <Box>
73
+ <PageHeader
74
+ title="Client created"
75
+ subtitle="Save the secret now — it cannot be retrieved later."
76
+ actions={
77
+ <Button variant="tertiary" onClick={() => navigate('/plugins/mcp-server/clients')}>
78
+ Back to clients
79
+ </Button>
80
+ }
81
+ />
82
+ <Box background="neutral0" padding={6} hasRadius shadow="tableShadow">
83
+ <Flex direction="column" gap={4} alignItems="stretch">
84
+ <Box>
85
+ <Typography variant="sigma" textColor="neutral600">
86
+ Client name
87
+ </Typography>
88
+ <Box paddingTop={1}>
89
+ <Typography variant="omega" fontWeight="semiBold">
90
+ {created.client.clientName}
91
+ </Typography>
92
+ </Box>
93
+ </Box>
94
+ <Box>
95
+ <Typography variant="sigma" textColor="neutral600">
96
+ client_id
97
+ </Typography>
98
+ <Box paddingTop={1}>
99
+ <Typography variant="omega">{created.client.clientId}</Typography>
100
+ </Box>
101
+ </Box>
102
+ {created.clientSecret && (
103
+ <Box background="warning100" padding={4} hasRadius>
104
+ <Typography variant="sigma" textColor="warning700">
105
+ client_secret (shown once)
106
+ </Typography>
107
+ <Box paddingTop={1}>
108
+ <Typography variant="omega" fontWeight="semiBold" textColor="warning700">
109
+ {created.clientSecret}
110
+ </Typography>
111
+ </Box>
112
+ </Box>
113
+ )}
114
+ </Flex>
115
+ </Box>
116
+ </Box>
117
+ );
118
+ }
119
+
120
+ return (
121
+ <Box>
122
+ <PageHeader
123
+ title="New OAuth client"
124
+ subtitle="Register an MCP client that will obtain access tokens via OAuth 2.1 + PKCE"
125
+ actions={
126
+ <Button
127
+ variant="tertiary"
128
+ startIcon={<ArrowLeft />}
129
+ onClick={() => navigate('/plugins/mcp-server/clients')}
130
+ >
131
+ Back
132
+ </Button>
133
+ }
134
+ />
135
+
136
+ {error && (
137
+ <Box background="danger100" padding={4} hasRadius marginBottom={6}>
138
+ <Typography textColor="danger700">{error}</Typography>
139
+ </Box>
140
+ )}
141
+
142
+ <Box background="neutral0" padding={6} hasRadius shadow="tableShadow">
143
+ <Flex direction="column" gap={6} alignItems="stretch">
144
+ <Grid.Root gap={6}>
145
+ <Grid.Item col={6} s={12} direction="column" alignItems="stretch">
146
+ <Box>
147
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">
148
+ Client name
149
+ </Typography>
150
+ <Box paddingTop={1}>
151
+ <TextInput
152
+ name="clientName"
153
+ value={name}
154
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
155
+ setName(e.target.value)
156
+ }
157
+ placeholder="e.g. Claude Desktop"
158
+ />
159
+ </Box>
160
+ </Box>
161
+ </Grid.Item>
162
+ <Grid.Item col={6} s={12} direction="column" alignItems="stretch">
163
+ <Box>
164
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">
165
+ Client type
166
+ </Typography>
167
+ <Box paddingTop={3}>
168
+ <Checkbox
169
+ checked={isConfidential}
170
+ onCheckedChange={(c: boolean | 'indeterminate') =>
171
+ setIsConfidential(c === true)
172
+ }
173
+ >
174
+ Confidential client (generates a client_secret)
175
+ </Checkbox>
176
+ </Box>
177
+ </Box>
178
+ </Grid.Item>
179
+ </Grid.Root>
180
+
181
+ <Box>
182
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">
183
+ Redirect URIs (optional)
184
+ </Typography>
185
+ <Box paddingTop={1} paddingBottom={1}>
186
+ <Typography variant="pi" textColor="neutral600">
187
+ Leave blank for CLI clients (Claude Code, Cursor, Codex, etc.) — defaults to <code>http://localhost/callback</code>, any loopback port matches. One per line, exact match for non-loopback URIs.
188
+ </Typography>
189
+ </Box>
190
+ <Textarea
191
+ name="redirectUris"
192
+ value={redirects}
193
+ onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
194
+ setRedirects(e.target.value)
195
+ }
196
+ placeholder="http://localhost/callback"
197
+ rows={3}
198
+ />
199
+ </Box>
200
+
201
+ <Box>
202
+ <Typography variant="pi" fontWeight="bold" textColor="neutral800">
203
+ Scopes
204
+ </Typography>
205
+ <Box paddingTop={1} paddingBottom={2}>
206
+ <Typography variant="pi" textColor="neutral600">
207
+ Permissions the client will request at authorization time.
208
+ </Typography>
209
+ </Box>
210
+ <Flex direction="column" gap={2} alignItems="stretch">
211
+ {ALL_SCOPES.map((s) => (
212
+ <Checkbox
213
+ key={s.id}
214
+ checked={scopes.includes(s.id)}
215
+ onCheckedChange={(c: boolean | 'indeterminate') => {
216
+ if (c === true) setScopes((prev) => [...prev, s.id]);
217
+ else setScopes((prev) => prev.filter((x) => x !== s.id));
218
+ }}
219
+ >
220
+ <Typography variant="omega">
221
+ <code>{s.id}</code> — {s.label}
222
+ </Typography>
223
+ </Checkbox>
224
+ ))}
225
+ </Flex>
226
+ </Box>
227
+
228
+ <Flex justifyContent="flex-end" gap={2}>
229
+ <Button
230
+ variant="tertiary"
231
+ onClick={() => navigate('/plugins/mcp-server/clients')}
232
+ disabled={submitting}
233
+ >
234
+ Cancel
235
+ </Button>
236
+ <Button loading={submitting} onClick={submit}>
237
+ Create client
238
+ </Button>
239
+ </Flex>
240
+ </Flex>
241
+ </Box>
242
+ </Box>
243
+ );
244
+ }