@12-apps/mcp 1.20.0 → 2.0.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.
- package/package.json +3 -3
- package/src/guide.ts +144 -98
- package/src/index.ts +3 -2
- package/src/react/ai-onboarding.tsx +54 -13
- package/src/react/index.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "App-agnostic MCP server core: generate one MCP tool per OpenAPI operation and proxy each call carrying the caller's bearer token (permission passthrough). Also ships the OAuth 2.1 authorization server (./oauth, ./hono: register/authorize/token, JWKS and both .well-known documents), the package-owned Prisma partial + migration for its three tables, the mcp:generate/mcp:check (./generate) and mcp:coverage (./coverage) gates, and the reusable AI-connect onboarding UI (./react).",
|
|
6
6
|
"exports": {
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@12-apps/onboarding": "^1.20.0",
|
|
27
|
-
"@12-apps/rbac": "^
|
|
28
|
-
"@12-apps/ui": "^4.
|
|
27
|
+
"@12-apps/rbac": "^3.0.0",
|
|
28
|
+
"@12-apps/ui": "^4.1.0",
|
|
29
29
|
"@mui/icons-material": "^6.5.0",
|
|
30
30
|
"jose": "^6.1.3",
|
|
31
31
|
"react": "^19.2.0"
|
package/src/guide.ts
CHANGED
|
@@ -99,104 +99,125 @@ const CONNECTOR_TAIL: readonly string[] = [
|
|
|
99
99
|
* stage 2 creates the connector and signs in — which registers the connection on
|
|
100
100
|
* the store side, so no prompt needs to be pasted afterwards.
|
|
101
101
|
*/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
label: "
|
|
102
|
+
function chatgptConfigureStages(
|
|
103
|
+
platformName: string,
|
|
104
|
+
): readonly AiHostConfigureStage[] {
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
id: "enable-dev-mode",
|
|
108
|
+
label: "enable developer mode",
|
|
109
|
+
link: {
|
|
110
|
+
url: "https://chatgpt.com/plugins#settings/Security",
|
|
111
|
+
label: "Abrir Segurança e login",
|
|
112
|
+
},
|
|
113
|
+
steps: [
|
|
114
|
+
"Ative o Modo desenvolvedor em Settings › Security and login (Segurança e login).",
|
|
115
|
+
],
|
|
109
116
|
},
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
{
|
|
118
|
+
id: "configurar",
|
|
119
|
+
label: "configurar",
|
|
120
|
+
link: {
|
|
121
|
+
url: "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins",
|
|
122
|
+
label: "Criar o conector",
|
|
123
|
+
},
|
|
124
|
+
steps: [
|
|
125
|
+
"Isso vai abrir um popup para você criar um plugin novo. Coloque como nome o nome da sua loja e, no campo MCP, o link copiado no passo anterior.",
|
|
126
|
+
'Marque a caixa "I understand and want to continue" — a OpenAI não revisou este servidor MCP; ela avisa que sites podem tentar roubar seus dados ou induzir o modelo a ações indevidas, incluindo destruir dados.',
|
|
127
|
+
`Clique em "Sign in with ${platformName}" e entre com a sua conta de lojista para autorizar o acesso. Pronto: a conexão é registrada automaticamente.`,
|
|
128
|
+
],
|
|
120
129
|
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
'Marque a caixa "I understand and want to continue" — a OpenAI não revisou este servidor MCP; ela avisa que sites podem tentar roubar seus dados ou induzir o modelo a ações indevidas, incluindo destruir dados.',
|
|
124
|
-
'Clique em "Sign in with Future Drink" e entre com a sua conta de lojista para autorizar o acesso. Pronto: a conexão é registrada automaticamente.',
|
|
125
|
-
],
|
|
126
|
-
},
|
|
127
|
-
];
|
|
130
|
+
];
|
|
131
|
+
}
|
|
128
132
|
|
|
129
133
|
/**
|
|
130
134
|
* The AI hosts a store owner can connect, in recommended order. Same OAuth flow
|
|
131
135
|
* everywhere (the host drives it) — only the menu path differs per app.
|
|
136
|
+
*
|
|
137
|
+
* A FUNCTION of the platform's name, because one step is not generic: the
|
|
138
|
+
* ChatGPT connector's consent screen shows an OAuth button labelled with
|
|
139
|
+
* whoever operates the server, and the owner is told which button to click. It
|
|
140
|
+
* used to name one particular STORE on one particular deployment — not even the
|
|
141
|
+
* product, a tenant of it — so every other adopter instructed its owners to
|
|
142
|
+
* click a button that does not exist.
|
|
132
143
|
*/
|
|
133
|
-
export
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
export function aiHostGuides(platformName: string): readonly AiHostGuide[] {
|
|
145
|
+
const chatgptStages = chatgptConfigureStages(platformName);
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
id: "claude",
|
|
149
|
+
label: "Claude.ai",
|
|
150
|
+
brand: "claude",
|
|
151
|
+
kind: "No navegador",
|
|
152
|
+
link: {
|
|
153
|
+
url: "https://claude.ai/new?modal=add-custom-connector#settings/customize-connectors",
|
|
154
|
+
label: "Abrir os conectores do Claude",
|
|
155
|
+
},
|
|
156
|
+
docs: {
|
|
157
|
+
url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
|
|
158
|
+
label: "documentação oficial da Anthropic — conectores personalizados",
|
|
159
|
+
},
|
|
160
|
+
steps: [
|
|
161
|
+
"Clique no botão acima (ou vá em Settings › Customize › Connectors) e escolha Add custom connector.",
|
|
162
|
+
"Dê um nome ao conector (ex.: o nome da sua loja) e cole a URL do servidor MCP da sua loja (copie acima) no campo de URL.",
|
|
163
|
+
...CONNECTOR_TAIL,
|
|
164
|
+
],
|
|
146
165
|
},
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
"
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
{
|
|
167
|
+
id: "claude-desktop",
|
|
168
|
+
label: "Claude Desktop",
|
|
169
|
+
brand: "claude",
|
|
170
|
+
kind: "Aplicativo (Windows/Mac)",
|
|
171
|
+
docs: {
|
|
172
|
+
url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
|
|
173
|
+
label: "documentação oficial da Anthropic — conectores personalizados",
|
|
174
|
+
},
|
|
175
|
+
steps: [
|
|
176
|
+
"Abra o Claude Desktop e vá em Settings (⚙️) › Connectors.",
|
|
177
|
+
"Clique em Add custom connector e cole a URL do servidor MCP da sua loja (copie acima).",
|
|
178
|
+
...CONNECTOR_TAIL,
|
|
179
|
+
],
|
|
161
180
|
},
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
{
|
|
182
|
+
id: "chatgpt",
|
|
183
|
+
label: "ChatGPT",
|
|
184
|
+
brand: "openai",
|
|
185
|
+
kind: "No navegador",
|
|
186
|
+
link: {
|
|
187
|
+
url: "https://chatgpt.com/plugins",
|
|
188
|
+
label: "Abrir os plugins do ChatGPT",
|
|
189
|
+
},
|
|
190
|
+
docs: {
|
|
191
|
+
url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
|
|
192
|
+
label:
|
|
193
|
+
"documentação oficial da OpenAI — conectar um servidor MCP ao ChatGPT",
|
|
194
|
+
},
|
|
195
|
+
configureStages: chatgptStages,
|
|
196
|
+
// Mirrors the flattened stage instructions so the MCP connect guide
|
|
197
|
+
// (`connectToChatGpt`) can never drift from what owners see in the wizard.
|
|
198
|
+
steps: chatgptStages.flatMap((stage) => stage.steps),
|
|
177
199
|
},
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
200
|
+
{
|
|
201
|
+
id: "codex",
|
|
202
|
+
label: "Codex",
|
|
203
|
+
brand: "openai",
|
|
204
|
+
kind: "App / CLI de desenvolvedor",
|
|
205
|
+
link: {
|
|
206
|
+
url: "https://developers.openai.com/codex",
|
|
207
|
+
label: "Documentação do Codex",
|
|
208
|
+
},
|
|
209
|
+
docs: {
|
|
210
|
+
url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
|
|
211
|
+
label: "documentação oficial da OpenAI — conectar um servidor MCP",
|
|
212
|
+
},
|
|
213
|
+
steps: [
|
|
214
|
+
"No Codex, abra as configurações de MCP (Settings › MCP no app, ou o arquivo de configuração na CLI).",
|
|
215
|
+
"Adicione um servidor MCP e cole a URL do servidor MCP da sua loja (copie acima) como um conector remoto (HTTP).",
|
|
216
|
+
...CONNECTOR_TAIL,
|
|
217
|
+
],
|
|
192
218
|
},
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
"Adicione um servidor MCP e cole a URL do servidor MCP da sua loja (copie acima) como um conector remoto (HTTP).",
|
|
196
|
-
...CONNECTOR_TAIL,
|
|
197
|
-
],
|
|
198
|
-
},
|
|
199
|
-
];
|
|
219
|
+
];
|
|
220
|
+
}
|
|
200
221
|
|
|
201
222
|
export interface AiCapability {
|
|
202
223
|
/** Stable id — maps to an icon in the component. */
|
|
@@ -217,7 +238,8 @@ export const AI_CAPABILITIES: readonly AiCapability[] = [
|
|
|
217
238
|
{
|
|
218
239
|
id: "inventory",
|
|
219
240
|
title: "Controle o estoque",
|
|
220
|
-
detail:
|
|
241
|
+
detail:
|
|
242
|
+
'"Quanto ainda tenho do produto X? Registre a entrada de 20 unidades."',
|
|
221
243
|
},
|
|
222
244
|
{
|
|
223
245
|
id: "catalog",
|
|
@@ -239,15 +261,39 @@ export const AI_CAPABILITIES: readonly AiCapability[] = [
|
|
|
239
261
|
export const AI_PERMISSION_MODEL =
|
|
240
262
|
"O assistente age em seu nome, com exatamente as suas permissões: ele pode fazer o que você pode fazer na sua loja — nada além disso. Não é preciso criar nenhuma chave ou credencial extra; a autorização usa o seu próprio login.";
|
|
241
263
|
|
|
264
|
+
/** The two tools the paste-in prompt drives, and what to call the store id. */
|
|
265
|
+
export interface AiConnectPromptSpec {
|
|
266
|
+
/**
|
|
267
|
+
* The tool that REGISTERS the connection server-side, so the store learns
|
|
268
|
+
* which assistant connected.
|
|
269
|
+
*/
|
|
270
|
+
announceTool: string;
|
|
271
|
+
/** A real READ tool, called straight after, to prove the access works. */
|
|
272
|
+
probeTool: string;
|
|
273
|
+
/** What that read returns, in the owner's own words ("o estoque da loja"). */
|
|
274
|
+
probeSubject: string;
|
|
275
|
+
/** What the assistant should ask for if it needs to identify the store. */
|
|
276
|
+
identifierName: string;
|
|
277
|
+
}
|
|
278
|
+
|
|
242
279
|
/**
|
|
243
|
-
* The message the owner pastes into the assistant's chat right after
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
280
|
+
* The message the owner pastes into the assistant's chat right after
|
|
281
|
+
* connecting: announce the connection, then read something real to prove it
|
|
282
|
+
* works. The owner types nothing.
|
|
283
|
+
*
|
|
284
|
+
* BUILT from the host's tool names rather than shipped with them. This was a
|
|
285
|
+
* constant naming two tools — `announceAiConnection` and `listInventory` — that
|
|
286
|
+
* THIS PACKAGE does not define or serve; they belong to one adopter's surface.
|
|
287
|
+
* Any other host handed its owner a prompt instructing the assistant to call
|
|
288
|
+
* two tools that do not exist, and because nothing registered the connection,
|
|
289
|
+
* the wizard's confirm step then waited forever for a state that could never
|
|
290
|
+
* arrive.
|
|
248
291
|
*/
|
|
249
|
-
export
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
292
|
+
export function aiConnectPrompt(spec: AiConnectPromptSpec): string {
|
|
293
|
+
return (
|
|
294
|
+
"Você agora tem acesso ao conector MCP da minha loja. Faça, nesta ordem:\n" +
|
|
295
|
+
`1) Execute a ferramenta ${spec.announceTool} informando qual assistente você é (host: "chatgpt", "claude" ou "codex") para registrar a conexão com a minha loja.\n` +
|
|
296
|
+
`2) Execute a ferramenta ${spec.probeTool} para confirmar o acesso a ${spec.probeSubject}.\n` +
|
|
297
|
+
`Se precisar do identificador da loja, me pergunte o ${spec.identifierName}.`
|
|
298
|
+
);
|
|
299
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -13,10 +13,11 @@ export * from "./types";
|
|
|
13
13
|
// truth for BOTH the `@12-apps/mcp/react` onboarding UI and the server-side connect
|
|
14
14
|
// tools, so what an agent reads via MCP cannot drift from what owners see.
|
|
15
15
|
export {
|
|
16
|
-
|
|
16
|
+
aiHostGuides,
|
|
17
17
|
AI_CAPABILITIES,
|
|
18
18
|
AI_PERMISSION_MODEL,
|
|
19
|
-
|
|
19
|
+
aiConnectPrompt,
|
|
20
|
+
type AiConnectPromptSpec,
|
|
20
21
|
providerForHostId,
|
|
21
22
|
type AiHostBrand,
|
|
22
23
|
type AiHostLink,
|
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
AI_CAPABILITIES,
|
|
13
|
-
|
|
14
|
-
AI_HOST_GUIDES,
|
|
13
|
+
aiHostGuides,
|
|
15
14
|
AI_PERMISSION_MODEL,
|
|
16
15
|
type AiCapability,
|
|
17
16
|
type AiHostGuide,
|
|
@@ -44,14 +43,33 @@ export interface AiIntegrationOnboardingProps {
|
|
|
44
43
|
featureKey?: string;
|
|
45
44
|
/** Show the dev-only "reset onboarding" button. @default false */
|
|
46
45
|
devReset?: boolean;
|
|
47
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* The platform operating this MCP server, as its OAuth consent button names
|
|
48
|
+
* it. REQUIRED, and it is the reason `hosts` can have a default at all: one
|
|
49
|
+
* ChatGPT step tells the owner which "Sign in with …" button to click, and
|
|
50
|
+
* that button carries whoever runs the server. It used to be a hard-coded
|
|
51
|
+
* name — of a single STORE on one deployment, not even the product — so every
|
|
52
|
+
* other adopter pointed its owners at a button that does not exist.
|
|
53
|
+
*/
|
|
54
|
+
platformName: string;
|
|
55
|
+
/** Assistants offered in the flow. @default aiHostGuides(platformName) */
|
|
48
56
|
hosts?: readonly AiHostGuide[];
|
|
49
57
|
/** Capability cards on the landing. @default the shared AI_CAPABILITIES */
|
|
50
58
|
capabilities?: readonly AiCapability[];
|
|
51
59
|
/** Permission reassurance copy on the landing. @default AI_PERMISSION_MODEL */
|
|
52
60
|
permissionModel?: string;
|
|
53
|
-
/**
|
|
54
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Message the owner pastes into the assistant on the Conectar step.
|
|
63
|
+
*
|
|
64
|
+
* REQUIRED, with no default, because the useful version of it names TOOLS —
|
|
65
|
+
* one to register the connection, one to read something real — and this
|
|
66
|
+
* package neither defines nor serves any. It shipped a constant naming two
|
|
67
|
+
* tools from one adopter's surface, so another host handed its owner a prompt
|
|
68
|
+
* that called two things that did not exist, and the confirm step then waited
|
|
69
|
+
* forever for a registration that could never happen. Build it with
|
|
70
|
+
* `aiConnectPrompt({ … })`.
|
|
71
|
+
*/
|
|
72
|
+
connectPrompt: string;
|
|
55
73
|
/**
|
|
56
74
|
* Re-check the live connection on the verify step's "Testar conexão" button —
|
|
57
75
|
* apps pass a router refresh (e.g. Next's `router.refresh`). @default a full
|
|
@@ -100,10 +118,19 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
|
|
|
100
118
|
devReset,
|
|
101
119
|
} = props;
|
|
102
120
|
const { state } = useOnboarding();
|
|
103
|
-
const selectedHost =
|
|
121
|
+
const selectedHost =
|
|
122
|
+
hosts.find((h) => h.id === state.data.selectedHost) ?? hosts[0]!;
|
|
104
123
|
|
|
105
|
-
const steps = buildFlowSteps({
|
|
106
|
-
|
|
124
|
+
const steps = buildFlowSteps({
|
|
125
|
+
host: selectedHost,
|
|
126
|
+
hosts,
|
|
127
|
+
endpointUrl,
|
|
128
|
+
connectPrompt,
|
|
129
|
+
connections,
|
|
130
|
+
onRetest,
|
|
131
|
+
});
|
|
132
|
+
const connectedHostId = (state.data.connectedHost ??
|
|
133
|
+
state.data.selectedHost) as string | undefined;
|
|
107
134
|
|
|
108
135
|
return (
|
|
109
136
|
<GuidedSection
|
|
@@ -111,13 +138,22 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
|
|
|
111
138
|
title="Conecte assistentes de IA à sua loja"
|
|
112
139
|
startLabel="Ver como conectar"
|
|
113
140
|
renderLanding={(start) => (
|
|
114
|
-
<AiLanding
|
|
141
|
+
<AiLanding
|
|
142
|
+
onStart={start}
|
|
143
|
+
permissionModel={permissionModel}
|
|
144
|
+
capabilities={capabilities}
|
|
145
|
+
/>
|
|
115
146
|
)}
|
|
116
147
|
configuredTitle={connectedTitle(connections, hosts, connectedHostId)}
|
|
117
148
|
configuredSummary={connectedSummary(connections)}
|
|
118
149
|
editLabel="Conectar IA"
|
|
119
150
|
completedContent={(nav) => (
|
|
120
|
-
<StatusBoard
|
|
151
|
+
<StatusBoard
|
|
152
|
+
nav={nav}
|
|
153
|
+
connections={connections}
|
|
154
|
+
hosts={hosts}
|
|
155
|
+
onDisconnect={onDisconnect}
|
|
156
|
+
/>
|
|
121
157
|
)}
|
|
122
158
|
devReset={devReset}
|
|
123
159
|
dataTestId="ai-onboarding"
|
|
@@ -143,17 +179,22 @@ export function AiIntegrationOnboarding({
|
|
|
143
179
|
connections,
|
|
144
180
|
featureKey = DEFAULT_FEATURE_KEY,
|
|
145
181
|
devReset = false,
|
|
146
|
-
|
|
182
|
+
platformName,
|
|
183
|
+
hosts = aiHostGuides(platformName),
|
|
147
184
|
capabilities = AI_CAPABILITIES,
|
|
148
185
|
permissionModel = AI_PERMISSION_MODEL,
|
|
149
|
-
connectPrompt
|
|
186
|
+
connectPrompt,
|
|
150
187
|
onRetest = () => {
|
|
151
188
|
if (typeof window !== "undefined") window.location.reload();
|
|
152
189
|
},
|
|
153
190
|
onDisconnect,
|
|
154
191
|
}: AiIntegrationOnboardingProps): React.JSX.Element {
|
|
155
192
|
return (
|
|
156
|
-
<OnboardingProvider
|
|
193
|
+
<OnboardingProvider
|
|
194
|
+
featureKey={featureKey}
|
|
195
|
+
store={store}
|
|
196
|
+
initialState={initialState}
|
|
197
|
+
>
|
|
157
198
|
<AiOnboardingFlow
|
|
158
199
|
endpointUrl={endpointUrl}
|
|
159
200
|
connections={connections}
|
package/src/react/index.ts
CHANGED
|
@@ -29,10 +29,11 @@ export { McpEndpointUrl } from "./mcp-endpoint-url";
|
|
|
29
29
|
export { HostBrandAvatar, CapabilityIcon } from "./ai-icons";
|
|
30
30
|
export { FeatureBadge, type FeatureBadgeItem } from "./feature-badge";
|
|
31
31
|
export {
|
|
32
|
-
|
|
32
|
+
aiHostGuides,
|
|
33
33
|
AI_CAPABILITIES,
|
|
34
34
|
AI_PERMISSION_MODEL,
|
|
35
|
-
|
|
35
|
+
aiConnectPrompt,
|
|
36
|
+
type AiConnectPromptSpec,
|
|
36
37
|
providerForHostId,
|
|
37
38
|
type AiHostBrand,
|
|
38
39
|
type AiHostLink,
|