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.
- package/LICENSE +21 -0
- package/README.md +415 -0
- package/admin/src/components/PageHeader.tsx +33 -0
- package/admin/src/components/Sidebar.tsx +138 -0
- package/admin/src/index.tsx +54 -0
- package/admin/src/lib/api.ts +27 -0
- package/admin/src/lib/applyQuery.ts +152 -0
- package/admin/src/pages/App.tsx +126 -0
- package/admin/src/pages/AuditLog.tsx +386 -0
- package/admin/src/pages/Clients.tsx +465 -0
- package/admin/src/pages/EditClient.tsx +248 -0
- package/admin/src/pages/HomePage.tsx +378 -0
- package/admin/src/pages/NewClient.tsx +244 -0
- package/admin/src/pages/Settings.tsx +514 -0
- package/admin/src/pages/SsoBridge.tsx +96 -0
- package/admin/src/pages/Tools.tsx +68 -0
- package/admin/src/pluginId.ts +1 -0
- package/admin/src/translations/en.json +8 -0
- package/package.json +105 -0
- package/server/src/bootstrap.ts +118 -0
- package/server/src/config/index.ts +290 -0
- package/server/src/content-types/audit-log/index.ts +3 -0
- package/server/src/content-types/audit-log/schema.json +32 -0
- package/server/src/content-types/index.ts +19 -0
- package/server/src/content-types/oauth-auth-code/index.ts +3 -0
- package/server/src/content-types/oauth-auth-code/schema.json +31 -0
- package/server/src/content-types/oauth-client/index.ts +3 -0
- package/server/src/content-types/oauth-client/schema.json +33 -0
- package/server/src/content-types/oauth-consent/index.ts +3 -0
- package/server/src/content-types/oauth-consent/schema.json +21 -0
- package/server/src/content-types/oauth-refresh-token/index.ts +3 -0
- package/server/src/content-types/oauth-refresh-token/schema.json +25 -0
- package/server/src/content-types/oauth-revocation/index.ts +3 -0
- package/server/src/content-types/oauth-revocation/schema.json +18 -0
- package/server/src/content-types/oauth-signing-key/index.ts +3 -0
- package/server/src/content-types/oauth-signing-key/schema.json +21 -0
- package/server/src/controllers/admin/audit.ts +30 -0
- package/server/src/controllers/admin/clients.ts +148 -0
- package/server/src/controllers/admin/dashboard.ts +28 -0
- package/server/src/controllers/admin/index.ts +15 -0
- package/server/src/controllers/admin/settings.ts +38 -0
- package/server/src/controllers/admin/tools.ts +23 -0
- package/server/src/controllers/index.ts +13 -0
- package/server/src/controllers/mcp.ts +168 -0
- package/server/src/controllers/oauth/authorize.ts +418 -0
- package/server/src/controllers/oauth/index.ts +15 -0
- package/server/src/controllers/oauth/introspect.ts +45 -0
- package/server/src/controllers/oauth/metadata.ts +86 -0
- package/server/src/controllers/oauth/mode-guard.ts +22 -0
- package/server/src/controllers/oauth/register.ts +109 -0
- package/server/src/controllers/oauth/token.ts +206 -0
- package/server/src/controllers/proxy.ts +81 -0
- package/server/src/destroy.ts +28 -0
- package/server/src/index.ts +23 -0
- package/server/src/policies/authenticate.ts +81 -0
- package/server/src/policies/index.ts +13 -0
- package/server/src/policies/origin.ts +50 -0
- package/server/src/policies/rateLimit.ts +27 -0
- package/server/src/policies/scope.ts +32 -0
- package/server/src/register.ts +48 -0
- package/server/src/routes/admin.ts +85 -0
- package/server/src/routes/index.ts +13 -0
- package/server/src/routes/mcp.ts +31 -0
- package/server/src/routes/oauth.ts +81 -0
- package/server/src/routes/proxy.ts +29 -0
- package/server/src/services/audit.ts +158 -0
- package/server/src/services/heartbeat.ts +76 -0
- package/server/src/services/index.ts +37 -0
- package/server/src/services/instance-id.ts +30 -0
- package/server/src/services/mcp-server.ts +100 -0
- package/server/src/services/oauth/audience.ts +26 -0
- package/server/src/services/oauth/auth-codes.ts +78 -0
- package/server/src/services/oauth/clients.ts +386 -0
- package/server/src/services/oauth/consent.ts +38 -0
- package/server/src/services/oauth/errors.ts +32 -0
- package/server/src/services/oauth/pkce.ts +34 -0
- package/server/src/services/oauth/scopes.ts +42 -0
- package/server/src/services/oauth/signing-keys.ts +166 -0
- package/server/src/services/oauth/tokens.ts +324 -0
- package/server/src/services/permissions.ts +87 -0
- package/server/src/services/proxy-client.ts +167 -0
- package/server/src/services/rate-limiter.ts +180 -0
- package/server/src/services/redis.ts +139 -0
- package/server/src/services/session-directory.ts +121 -0
- package/server/src/services/session-store.ts +216 -0
- package/server/src/services/sso-cookie.ts +146 -0
- package/server/src/services/tools/content.ts +284 -0
- package/server/src/services/tools/index.ts +23 -0
- 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
|
+
}
|