@12-apps/mcp 1.20.0 → 3.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/ADOPTING.md +7 -7
- package/package.json +4 -4
- package/prisma/mcp.prisma +1 -1
- package/prisma/migrations/20260812150000_add_mcp_oauth_tables/migration.sql +6 -6
- package/scripts/sync-mcp-schema.mjs +2 -2
- package/src/coverage-gate/index.ts +1 -1
- package/src/coverage-gate/route-methods.ts +1 -1
- package/src/generate/index.ts +1 -1
- package/src/guide.ts +144 -98
- package/src/index.ts +3 -2
- package/src/oauth/access-token.ts +1 -1
- package/src/oauth/authorization-code.ts +1 -1
- package/src/oauth/authorize.ts +1 -1
- package/src/oauth/clients.ts +2 -2
- package/src/oauth/code-replay.ts +1 -1
- package/src/oauth/config.ts +3 -3
- package/src/oauth/context.ts +5 -5
- package/src/oauth/keys.ts +4 -4
- package/src/oauth/pkce.ts +1 -1
- package/src/oauth/refresh.ts +1 -1
- package/src/oauth/register.ts +1 -1
- package/src/oauth/token-grants.ts +2 -2
- package/src/oauth/token-response.ts +1 -1
- package/src/react/ai-onboarding.tsx +54 -13
- package/src/react/index.ts +3 -2
- package/src/server/manifest.ts +1 -1
- package/src/server/surface-lock.ts +1 -1
package/ADOPTING.md
CHANGED
|
@@ -34,7 +34,7 @@ library updates, every host updates with **no app changes**. Same contract
|
|
|
34
34
|
`enabled: false` authorize/token/jwks/discovery answer **404** — a probe cannot
|
|
35
35
|
tell a disabled AS from an app that has none — while registration answers
|
|
36
36
|
**403 `access_denied`**, because RFC 7591 has a code for "the endpoint is here,
|
|
37
|
-
registration is closed".
|
|
37
|
+
registration is closed". The origin host passes
|
|
38
38
|
`enabled: () => process.env.MCP_BEARER_ENABLED === 'true'`, so the surface stays
|
|
39
39
|
OFF until an operator opts in.
|
|
40
40
|
4. **No signing key, no tokens.** `signingKey` defaults to the env-backed provider
|
|
@@ -51,7 +51,7 @@ library updates, every host updates with **no app changes**. Same contract
|
|
|
51
51
|
the internal origin rather than to an attacker's. Issuance and verification read
|
|
52
52
|
the same resolver from the same request, which is what stops "minted for A,
|
|
53
53
|
verified against B" from rejecting valid tokens.
|
|
54
|
-
`trustedOriginsFromEnv('MCP_OAUTH_TRUSTED_ORIGINS')` keeps
|
|
54
|
+
`trustedOriginsFromEnv('MCP_OAUTH_TRUSTED_ORIGINS')` keeps the origin host's wiring.
|
|
55
55
|
6. **The stores are narrow ports; Prisma fills them in one line.**
|
|
56
56
|
`createPrismaMcpStores(async () => prisma as unknown as McpOauthPrisma)`. A
|
|
57
57
|
non-Prisma host implements `OAuthClientStore` / `RefreshTokenStore` /
|
|
@@ -104,7 +104,7 @@ library updates, every host updates with **no app changes**. Same contract
|
|
|
104
104
|
default, and it is deliberately the inconvenient one.
|
|
105
105
|
10. **Connections are per USER, not per tenant** — an MCP bearer is
|
|
106
106
|
auth-passthrough. `connections.resolveUserId(email)` maps the token's email to
|
|
107
|
-
the host's user id (
|
|
107
|
+
the host's user id (the origin host resolves it by email because `session.user.id` is
|
|
108
108
|
the OAuth `sub`); returning `null` records nothing. Recording is best-effort and
|
|
109
109
|
FENCED: a failing directory can never turn a valid grant into a 500, and nothing
|
|
110
110
|
about the attempt is logged, because the only values in hand are an email and a
|
|
@@ -211,7 +211,7 @@ Two things about the gates worth knowing before you adopt them:
|
|
|
211
211
|
committed exclusions file the moment it adopted the package, so it needs its own
|
|
212
212
|
burn-down).
|
|
213
213
|
|
|
214
|
-
## Phase B — adopting into a host that ALREADY has these tables (
|
|
214
|
+
## Phase B — adopting into a host that ALREADY has these tables (the origin host)
|
|
215
215
|
|
|
216
216
|
**Nothing to baseline.** Every statement in the package migration is guarded
|
|
217
217
|
(`CREATE TABLE IF NOT EXISTS`, `CREATE [UNIQUE] INDEX IF NOT EXISTS`, `ADD COLUMN
|
|
@@ -223,8 +223,8 @@ a host that already has `oauth_clients` / `oauth_refresh_tokens` /
|
|
|
223
223
|
Deliberate deltas to reconcile:
|
|
224
224
|
|
|
225
225
|
- **The FK from `mcp_connections.user_id` to `users` is not in the package
|
|
226
|
-
migration** — host vocabulary.
|
|
227
|
-
- **`onboarding_states` is not here.**
|
|
226
|
+
migration** — host vocabulary. The origin host keeps its `ON DELETE CASCADE`.
|
|
227
|
+
- **`onboarding_states` is not here.** The origin host's migration created it beside
|
|
228
228
|
`mcp_connections`; it belongs to `@12-apps/onboarding` (12-23).
|
|
229
229
|
- The host's `lib/mcp/oauth/**` (~1.5k LOC) and its four route files are replaced
|
|
230
230
|
by the mount plus, where a coverage gate forces the file to exist, a one-line
|
|
@@ -242,7 +242,7 @@ Deliberate deltas to reconcile:
|
|
|
242
242
|
and redactions, is the host's catalogue. The package generates, dispatches and
|
|
243
243
|
gates it.
|
|
244
244
|
- **`mcp:lint`, `mcp:parity`, `mcp:smoke`, `mcp:test-coverage`** — the remaining
|
|
245
|
-
|
|
245
|
+
the origin host MCP scripts. Only the two the reusable CI workflows shell out to moved
|
|
246
246
|
(12-23's scope).
|
|
247
247
|
- **Authorization codes as rows.** They are stateless signed blobs, so there is no
|
|
248
248
|
table and nothing to sweep — only the replay store (rule 8).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@12-apps/mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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": {
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
"prisma:sync:check": "node scripts/sync-mcp-schema.mjs --check"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@12-apps/onboarding": "^
|
|
27
|
-
"@12-apps/rbac": "^
|
|
28
|
-
"@12-apps/ui": "^
|
|
26
|
+
"@12-apps/onboarding": "^2.0.0",
|
|
27
|
+
"@12-apps/rbac": "^4.0.0",
|
|
28
|
+
"@12-apps/ui": "^5.0.0",
|
|
29
29
|
"@mui/icons-material": "^6.5.0",
|
|
30
30
|
"jose": "^6.1.3",
|
|
31
31
|
"react": "^19.2.0"
|
package/prisma/mcp.prisma
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Host-agnostic by design (the entity-lifecycle / rbac doctrine): `user_id` is a
|
|
13
13
|
// by-value scalar with NO relation, because this package cannot know the name of
|
|
14
|
-
// the host's user model. The host's own migration may add the FK (
|
|
14
|
+
// the host's user model. The host's own migration may add the FK (the origin host's
|
|
15
15
|
// is ON DELETE CASCADE). Note there is deliberately no `oauth_codes` table:
|
|
16
16
|
// authorization codes are STATELESS signed blobs, so there is nothing to store
|
|
17
17
|
// and nothing to sweep.
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
-- Authorization codes are deliberately absent: they are STATELESS signed blobs,
|
|
19
19
|
-- so there is no table to create and nothing to sweep.
|
|
20
20
|
--
|
|
21
|
-
-- The columns, defaults, indexes and CHECK are
|
|
21
|
+
-- The columns, defaults, indexes and CHECK are the origin host's
|
|
22
22
|
-- `20260713120000_add_oauth_client_refresh`,
|
|
23
23
|
-- `20260715180000_add_onboarding_state_mcp_connection` (the mcp_connections half
|
|
24
24
|
-- — the onboarding half belongs to @12-apps/onboarding) and
|
|
25
25
|
-- `20260720120000_add_mcp_connection_host` verbatim, minus the FK to `users`:
|
|
26
26
|
-- this package cannot know the name of a host's user table, and a host that has
|
|
27
|
-
-- one keeps its own constraint (
|
|
27
|
+
-- one keeps its own constraint (the origin host's is ON DELETE CASCADE).
|
|
28
28
|
--
|
|
29
29
|
-- EVERY statement is guarded (`IF NOT EXISTS`, and a conrelid-scoped DO block for
|
|
30
30
|
-- the CHECK, which has no IF NOT EXISTS form). That is what makes adoption by a
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
-- the difference bites exactly the host this file is written for: `CREATE TABLE IF
|
|
36
36
|
-- NOT EXISTS` skips the whole table, columns included, so a host holding an OLDER
|
|
37
37
|
-- shape of one of these tables silently keeps it. Each table below is therefore
|
|
38
|
-
-- followed by a guarded `ADD COLUMN` for every column that reached
|
|
38
|
+
-- followed by a guarded `ADD COLUMN` for every column that reached the origin host in a
|
|
39
39
|
-- LATER migration than its own CREATE. The full audit: `oauth_refresh_tokens
|
|
40
40
|
-- .user_sub` (`20260713150000_add_oauth_refresh_user_sub`) and `mcp_connections
|
|
41
41
|
-- .host` (`20260720120000_add_mcp_connection_host`). `oauth_clients` needs none —
|
|
@@ -112,10 +112,10 @@ CREATE INDEX IF NOT EXISTS "oauth_refresh_tokens_user_email_client_id_idx"
|
|
|
112
112
|
-- `CREATE TABLE IF NOT EXISTS` skips the WHOLE table, so a host that already holds
|
|
113
113
|
-- `oauth_refresh_tokens` in an OLDER SHAPE gets none of the columns declared above
|
|
114
114
|
-- — statement-level guarding is not the same as column-level guarding. That is
|
|
115
|
-
-- precisely how
|
|
115
|
+
-- precisely how the origin host's own history ran: `user_sub` arrived in a SECOND
|
|
116
116
|
-- migration (FUT-105, `20260713150000_add_oauth_refresh_user_sub`), so a host
|
|
117
117
|
-- frozen before it would adopt this file, skip the CREATE, never get the column,
|
|
118
|
-
-- and then fail on every refresh the package serves. Mirror
|
|
118
|
+
-- and then fail on every refresh the package serves. Mirror the origin host's pair
|
|
119
119
|
-- verbatim — guarded add with a backfill default to satisfy NOT NULL, then drop
|
|
120
120
|
-- the default so the column matches the Prisma schema (`String`, no default).
|
|
121
121
|
-- Both statements are no-ops on a fresh host and on a replay.
|
|
@@ -147,6 +147,6 @@ CREATE INDEX IF NOT EXISTS "mcp_connections_last_active_at_idx"
|
|
|
147
147
|
ON "mcp_connections"("last_active_at");
|
|
148
148
|
|
|
149
149
|
-- A host adopting this migration where `mcp_connections` predates the `host`
|
|
150
|
-
-- column (
|
|
150
|
+
-- column (the origin host added it in a later migration) gets it here; a fresh host
|
|
151
151
|
-- already has it from the CREATE above, so the guard makes both cases a no-op.
|
|
152
152
|
ALTER TABLE "mcp_connections" ADD COLUMN IF NOT EXISTS "host" TEXT;
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Only the schema partial. MIGRATIONS ARE NOT HANDLED HERE — the host
|
|
16
16
|
* discovers and copies them structurally, by looking for a `prisma/migrations`
|
|
17
|
-
* directory inside every installed `@12-apps/*` package (see
|
|
17
|
+
* directory inside every installed `@12-apps/*` package (see the origin host's
|
|
18
18
|
* packages/prisma/scripts/sync-prisma-plugins.mjs).
|
|
19
19
|
*
|
|
20
20
|
* The host package that owns the schema folder MUST also declare this package
|
|
21
21
|
* as a dependency, so the source of the copy is present in every build
|
|
22
22
|
* context.
|
|
23
23
|
*
|
|
24
|
-
* Default host path follows the
|
|
24
|
+
* Default host path follows the origin host layout
|
|
25
25
|
* (`packages/prisma/prisma/schema/`); another repo passes its own schema
|
|
26
26
|
* folder as the positional argument, or sets MCP_HOST_SCHEMA_DIR.
|
|
27
27
|
*/
|
|
@@ -6,7 +6,7 @@ import { collectRouteMethods } from "./route-methods";
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* `@12-apps/mcp/coverage` — the MCP route/action coverage gate (12-23), moved out
|
|
9
|
-
* of
|
|
9
|
+
* of the origin host's `apps/web/scripts/mcp/coverage.ts` so a host's own script is a
|
|
10
10
|
* one-line re-export and the CI workflow that shells out to the consumer's
|
|
11
11
|
* `mcp:coverage` package script (`12-apps/ci`'s `mcp-contract.yml`) keeps working
|
|
12
12
|
* unchanged.
|
|
@@ -11,7 +11,7 @@ import { exportedNamesOf, urlPathOf, walkRouteFiles } from "@12-apps/rbac/covera
|
|
|
11
11
|
* walk (`walkRouteFiles`), the URL mapping (`urlPathOf`) AND the export-head
|
|
12
12
|
* parser (`exportedNamesOf`) — and that is deliberate: both gates assert a
|
|
13
13
|
* COMPLETENESS property over the same two surfaces (`app/**` route files and
|
|
14
|
-
* `*actions.ts` modules), and
|
|
14
|
+
* `*actions.ts` modules), and the origin host's own comment on the shared scanner says
|
|
15
15
|
* why they must share it — "so the two gates can never disagree about what the
|
|
16
16
|
* surface is". Two copies would agree on the day they were written and drift
|
|
17
17
|
* silently after, in the direction of not looking. What is left here is the one
|
package/src/generate/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { ToolManifest } from "../types";
|
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* `@12-apps/mcp/generate` — the `mcp:generate` / `mcp:check` gate (12-23), moved
|
|
16
|
-
* out of
|
|
16
|
+
* out of the origin host's `apps/web/scripts/mcp/generate.ts` so a host's own script is
|
|
17
17
|
* a one-line call and `12-apps/ci`'s `mcp-contract.yml`, which shells out to the
|
|
18
18
|
* consumer's `mcp:check` package script, keeps working unchanged.
|
|
19
19
|
*
|
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,
|
|
@@ -4,7 +4,7 @@ import { issuer, resourceAudience, DEFAULT_MCP_RESOURCE_PATH, type McpScope } fr
|
|
|
4
4
|
import { SIGNING_ALG, type McpSigningKeyProvider } from "./keys";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* JWT access-token issuer + verifier (12-23, ported from
|
|
7
|
+
* JWT access-token issuer + verifier (12-23, ported from the origin host's
|
|
8
8
|
* `lib/mcp/oauth/jwt.ts` — behaviour unchanged; the signing key arrives through a
|
|
9
9
|
* provider and the resource path is config).
|
|
10
10
|
*
|
|
@@ -4,7 +4,7 @@ import { issuer } from "./config";
|
|
|
4
4
|
import { SIGNING_ALG, type McpSigningKeyProvider } from "./keys";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Stateless authorization-code mint/verify (12-23, ported from
|
|
7
|
+
* Stateless authorization-code mint/verify (12-23, ported from the origin host's
|
|
8
8
|
* `lib/mcp/oauth/authorization-code.ts` — behaviour unchanged; the signing key
|
|
9
9
|
* arrives through a provider instead of an env read).
|
|
10
10
|
*
|
package/src/oauth/authorize.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { StoredOAuthClient } from "./stores";
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The OAuth 2.1 Authorization Code + PKCE authorization endpoint (12-23, ported
|
|
9
|
-
* from
|
|
9
|
+
* from the origin host's `app/api/oauth/authorize/route.ts`).
|
|
10
10
|
*
|
|
11
11
|
* It renders no UI: it authenticates the caller against the host's cookie session
|
|
12
12
|
* (through `resolveSession`), validates the request, and either 302-redirects an
|
package/src/oauth/clients.ts
CHANGED
|
@@ -8,7 +8,7 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Client registration and the open-redirect guard (12-23, ported from
|
|
11
|
-
*
|
|
11
|
+
* the origin host's `lib/mcp/oauth/clients.ts`).
|
|
12
12
|
*
|
|
13
13
|
* A registered client is an external host (a Claude.ai / ChatGPT connector) from
|
|
14
14
|
* RFC 7591 dynamic client registration, or a static registration an operator
|
|
@@ -122,7 +122,7 @@ export interface ProviderAttributionRule {
|
|
|
122
122
|
provider: string;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
/**
|
|
125
|
+
/** The origin host's rules, and a sane default for any host talking to the same two. */
|
|
126
126
|
export const DEFAULT_PROVIDER_ROOTS: readonly ProviderAttributionRule[] = [
|
|
127
127
|
{ roots: ["claude.ai", "anthropic.com"], provider: "claude" },
|
|
128
128
|
{ roots: ["chatgpt.com", "openai.com"], provider: "chatgpt" },
|
package/src/oauth/code-replay.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Single-use guard for the stateless authorization codes (12-23, ported from
|
|
3
|
-
*
|
|
3
|
+
* the origin host's `lib/mcp/oauth/token-replay.ts`).
|
|
4
4
|
*
|
|
5
5
|
* A code is a signed blob with a `jti`, so "already redeemed" has to be remembered
|
|
6
6
|
* somewhere. The in-process option remembers it IN THIS PROCESS: a small map of
|
package/src/oauth/config.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The OAuth 2.1 authorization-server foundation: the shared scope source, the
|
|
3
3
|
* issuer/audience derivation, and the trusted-origin resolver every URL in the
|
|
4
|
-
* surface is built from (12-23, ported from
|
|
4
|
+
* surface is built from (12-23, ported from the origin host's
|
|
5
5
|
* `lib/mcp/oauth/config.ts`).
|
|
6
6
|
*
|
|
7
7
|
* Keeping the scopes and the origin resolution in ONE place is what stops the
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*
|
|
12
12
|
* What was env-reading in the host is CONFIG here (the package must not learn a
|
|
13
13
|
* host's variable names); `trustedOriginsFromEnv` is the one-line helper that
|
|
14
|
-
* keeps
|
|
14
|
+
* keeps the origin host's wiring identical.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/** Scopes advertised by both discovery documents. `mcp:write` gates mutating tools. */
|
|
@@ -66,7 +66,7 @@ function normalizeOrigins(origins: readonly string[]): string[] {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Read a comma-separated allowlist out of an environment variable —
|
|
69
|
+
* Read a comma-separated allowlist out of an environment variable — the origin host
|
|
70
70
|
* passes `trustedOriginsFromEnv('MCP_OAUTH_TRUSTED_ORIGINS')`, so the behaviour
|
|
71
71
|
* is identical while the variable's NAME stays the host's.
|
|
72
72
|
*/
|
package/src/oauth/context.ts
CHANGED
|
@@ -25,7 +25,7 @@ import type { McpOauthStores, StoredOAuthClient } from "./stores";
|
|
|
25
25
|
/** The identity an authorize request binds a code to. From the SESSION only. */
|
|
26
26
|
export interface McpOauthSession {
|
|
27
27
|
/**
|
|
28
|
-
* The OAuth subject (
|
|
28
|
+
* The OAuth subject (the origin host passes the Google `sub`, falling back to the
|
|
29
29
|
* email). Carried through every rotation so a refreshed token keeps the same
|
|
30
30
|
* stable `sub`.
|
|
31
31
|
*/
|
|
@@ -45,7 +45,7 @@ export interface McpOauthPaths {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export const DEFAULT_OAUTH_PATHS: McpOauthPaths = {
|
|
48
|
-
//
|
|
48
|
+
// the origin host's paths, and the ones the RFC 8414 document has always advertised.
|
|
49
49
|
authorize: "/api/oauth/authorize",
|
|
50
50
|
token: "/api/oauth/token",
|
|
51
51
|
register: "/api/oauth/register",
|
|
@@ -78,12 +78,12 @@ export interface McpOauthConfig {
|
|
|
78
78
|
resolveSession: (request: Request) => Promise<McpOauthSession | null> | McpOauthSession | null;
|
|
79
79
|
/**
|
|
80
80
|
* The operator gate. `false` makes the whole surface inert — authorize/token/jwks
|
|
81
|
-
* answer 404 and registration answers 403 — which is how
|
|
81
|
+
* answer 404 and registration answers 403 — which is how the origin host ships it OFF
|
|
82
82
|
* by default (`MCP_BEARER_ENABLED`). Default: enabled (mounting is the opt-in).
|
|
83
83
|
*/
|
|
84
84
|
enabled?: boolean | (() => boolean);
|
|
85
85
|
/**
|
|
86
|
-
* Signing material. Default: the env-backed provider with
|
|
86
|
+
* Signing material. Default: the env-backed provider with the origin host's variable
|
|
87
87
|
* names. `null` from the provider means "not provisioned": nothing is minted and
|
|
88
88
|
* the JWKS answers 503 rather than falling back to a weaker mode.
|
|
89
89
|
*/
|
|
@@ -216,7 +216,7 @@ export function resolveMcpOauthConfig(config: McpOauthConfig): McpOauthContext {
|
|
|
216
216
|
stores: config.stores,
|
|
217
217
|
resolveSession: config.resolveSession,
|
|
218
218
|
// Mounting is the opt-in, so the gate defaults to ON; a host that ships the
|
|
219
|
-
// surface dark passes its own flag (
|
|
219
|
+
// surface dark passes its own flag (the origin host: `MCP_BEARER_ENABLED`).
|
|
220
220
|
enabled: typeof enabled === "function" ? enabled : () => enabled,
|
|
221
221
|
// `null` from the provider means "not provisioned": nothing is minted and the
|
|
222
222
|
// JWKS answers 503 rather than falling back to a weaker mode.
|
package/src/oauth/keys.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { exportJWK, importPKCS8, type CryptoKey, type JWK } from "jose";
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Signing-key / JWK loading for the OAuth authorization server (12-23, ported
|
|
5
|
-
* from
|
|
5
|
+
* from the origin host's `lib/mcp/oauth/keys.ts`).
|
|
6
6
|
*
|
|
7
7
|
* ES256 (P-256) from PEM material, the published public JWK (with `kid` for
|
|
8
8
|
* rotation), and a safe-by-default absence signal (`null`) when no key is
|
|
@@ -10,7 +10,7 @@ import { exportJWK, importPKCS8, type CryptoKey, type JWK } from "jose";
|
|
|
10
10
|
* rather than falling back to a weaker mode while the surface is mounted.
|
|
11
11
|
*
|
|
12
12
|
* WHERE the PEM comes from is the host's business: `loadSigningKeyFromEnv` keeps
|
|
13
|
-
*
|
|
13
|
+
* the origin host's env-var wiring, and any other provider (a secrets manager, a KMS
|
|
14
14
|
* export) satisfies the same `McpSigningKeyProvider` shape.
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -87,13 +87,13 @@ export function signingKeyProvider(
|
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
/** Env var carrying the ES256 private key as a PKCS#8 PEM (
|
|
90
|
+
/** Env var carrying the ES256 private key as a PKCS#8 PEM (the origin host's name). */
|
|
91
91
|
export const DEFAULT_SIGNING_KEY_ENV = "MCP_OAUTH_SIGNING_KEY";
|
|
92
92
|
/** Env var carrying the key id (`kid`) used to select the key during rotation. */
|
|
93
93
|
export const DEFAULT_SIGNING_KEY_ID_ENV = "MCP_OAUTH_SIGNING_KEY_ID";
|
|
94
94
|
|
|
95
95
|
/**
|
|
96
|
-
* The env-backed provider —
|
|
96
|
+
* The env-backed provider — the origin host's wiring, kept identical, with the
|
|
97
97
|
* variable names as arguments so the package states no host's vocabulary.
|
|
98
98
|
*/
|
|
99
99
|
export function loadSigningKeyFromEnv(
|
package/src/oauth/pkce.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PKCE (RFC 7636) S256 challenge helpers for the OAuth authorization server
|
|
3
|
-
* (12-23, ported verbatim from
|
|
3
|
+
* (12-23, ported verbatim from the origin host's `lib/mcp/oauth/pkce.ts`).
|
|
4
4
|
*
|
|
5
5
|
* OAuth 2.1 mandates the `S256` code-challenge method and forbids `plain`, so
|
|
6
6
|
* this module computes `BASE64URL(SHA-256(code_verifier))` and compares it to
|
package/src/oauth/refresh.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
3
3
|
import type { NewRefreshToken, RefreshTokenStore, StoredRefreshToken } from "./stores";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Refresh-token issue + rotation (12-23, ported from
|
|
6
|
+
* Refresh-token issue + rotation (12-23, ported from the origin host's
|
|
7
7
|
* `lib/mcp/oauth/refresh.ts` — behaviour unchanged; Prisma calls became the
|
|
8
8
|
* `RefreshTokenStore` port).
|
|
9
9
|
*
|
package/src/oauth/register.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { McpOauthContext } from "./context";
|
|
|
3
3
|
import type { TokenEndpointAuthMethod } from "./stores";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* RFC 7591 Dynamic Client Registration (12-23, ported from
|
|
6
|
+
* RFC 7591 Dynamic Client Registration (12-23, ported from the origin host's
|
|
7
7
|
* `app/api/oauth/register/route.ts`).
|
|
8
8
|
*
|
|
9
9
|
* An external host (a Claude.ai / ChatGPT connector) self-registers by POSTing RFC
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "./token-response";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* The two grant handlers of the token endpoint (12-23, ported from
|
|
22
|
+
* The two grant handlers of the token endpoint (12-23, ported from the origin host's
|
|
23
23
|
* `lib/mcp/oauth/token-grants.ts`).
|
|
24
24
|
*
|
|
25
25
|
* Security invariants enforced here, unchanged:
|
|
@@ -123,7 +123,7 @@ function readAuthorizationCodeParams(form: URLSearchParams): AuthorizationCodePa
|
|
|
123
123
|
/**
|
|
124
124
|
* Redeem a presented code, or refuse.
|
|
125
125
|
*
|
|
126
|
-
* The ORDER is the security contract, and it is the order
|
|
126
|
+
* The ORDER is the security contract, and it is the order the origin host established:
|
|
127
127
|
* verify the code's signature, authenticate the presenting client against the
|
|
128
128
|
* client the code was bound to, check the bound `redirect_uri`, check PKCE — and
|
|
129
129
|
* only THEN consume the single-use `jti`. Consuming earlier would let a failed
|
|
@@ -5,7 +5,7 @@ import type { OAuthClientStore } from "./stores";
|
|
|
5
5
|
/**
|
|
6
6
|
* The token endpoint's wire helpers: the RFC 6749 §5.1/§5.2 bodies and client
|
|
7
7
|
* authentication (12-23, split out of the grant handlers so each file stays under
|
|
8
|
-
* the size gate — the same split
|
|
8
|
+
* the size gate — the same split the origin host made).
|
|
9
9
|
*
|
|
10
10
|
* These bodies are NOT the house `{ data }` envelope, deliberately: they are read
|
|
11
11
|
* by OAuth clients that expect the RFC shapes at the top level, and `Cache-Control:
|
|
@@ -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,
|
package/src/server/manifest.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { GeneratedTool, ToolManifest } from "../types";
|
|
|
11
11
|
export interface BuildManifestOptions {
|
|
12
12
|
/** Bumped intentionally on any tool-shape change (mirrors the golden catalog). */
|
|
13
13
|
version: number;
|
|
14
|
-
/** Human label for the spec, e.g. "
|
|
14
|
+
/** Human label for the spec, e.g. "acme web @ openapi.json". */
|
|
15
15
|
source: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -16,7 +16,7 @@ import type { GeneratedTool } from "../types";
|
|
|
16
16
|
* ask again: a tool that shipped stays invisible to every ALREADY CONNECTED
|
|
17
17
|
* client for as long as that connection lives.
|
|
18
18
|
*
|
|
19
|
-
* That is not a hypothetical. In
|
|
19
|
+
* That is not a hypothetical. In the origin host a new tool reached production,
|
|
20
20
|
* answered on its route, and did not appear in a live connector — behind a
|
|
21
21
|
* `serverInfo.version` frozen at its initial value while ~280 tools were added
|
|
22
22
|
* underneath it. Nothing was broken; the only thing asking anyone to bump it was
|