@12-apps/mcp 3.10.0 → 3.12.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/dist/chunk-VDD4YRNP.js +197 -0
- package/dist/chunk-VDD4YRNP.js.map +1 -0
- package/dist/chunk-WUNMAHQG.js +325 -0
- package/dist/chunk-WUNMAHQG.js.map +1 -0
- package/dist/{create-api-mcp-oauth-CwVXKK-A.d.ts → create-api-mcp-oauth-CsC0jlH7.d.ts} +1 -1
- package/dist/hono/index.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -9
- package/dist/index.js.map +1 -1
- package/dist/locales-eKE_OJw4.d.ts +64 -0
- package/dist/manifest/index.d.ts +93 -0
- package/dist/manifest/index.js +22 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/manifest/server.d.ts +74 -0
- package/dist/manifest/server.js +41 -0
- package/dist/manifest/server.js.map +1 -0
- package/dist/oauth/index.d.ts +2 -2
- package/dist/oauth/index.js +6 -189
- package/dist/oauth/index.js.map +1 -1
- package/dist/react/index.d.ts +20 -2
- package/dist/react/index.js +129 -1
- package/dist/react/index.js.map +1 -1
- package/package.json +34 -14
- package/src/en-US.ts +204 -0
- package/src/index.ts +12 -0
- package/src/locales.ts +58 -0
- package/src/manifest/index.ts +89 -0
- package/src/manifest/server.ts +94 -0
- package/src/react/en-US.ts +125 -0
- package/src/react/index.ts +18 -0
- package/src/react/locales.ts +17 -0
- package/dist/chunk-HRAQKMDC.js +0 -154
- package/dist/chunk-HRAQKMDC.js.map +0 -1
- package/dist/pt-BR-e1RnV2v8.d.ts +0 -25
package/src/en-US.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AiCapability,
|
|
3
|
+
AiConnectPromptSpec,
|
|
4
|
+
AiHostConfigureStage,
|
|
5
|
+
AiHostGuide,
|
|
6
|
+
AiPermissionModel,
|
|
7
|
+
} from "./guide";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The en-US pack for the AI-integration surface — NAMED constants a host passes
|
|
11
|
+
* by hand, never defaults.
|
|
12
|
+
*
|
|
13
|
+
* The guides are DATA as much as copy: which assistants are offered, their
|
|
14
|
+
* stage ids, their brands and their LINKS. Both halves travel together because
|
|
15
|
+
* a step label and the stage it labels are useless apart — and the parts that
|
|
16
|
+
* are not words do not change between languages:
|
|
17
|
+
*
|
|
18
|
+
* - `id` and `brand` are the package's own keys, matched on by the component;
|
|
19
|
+
* - the URLs point at Anthropic's and OpenAI's own pages;
|
|
20
|
+
* - the UI labels a reader must FIND in those products stay in the product's
|
|
21
|
+
* own English (`Add custom connector`, `Settings › Security and login`,
|
|
22
|
+
* `Sign in with …`), which is the same rule that keeps a vendor's field name
|
|
23
|
+
* untranslated everywhere else here.
|
|
24
|
+
*
|
|
25
|
+
* The pt-BR pack quotes those same labels in English for exactly this reason;
|
|
26
|
+
* what differs between the two packs is the instruction around them.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const CONNECTOR_TAIL: readonly string[] = [
|
|
30
|
+
"Leave OAuth Client ID and Client Secret blank — there are no credentials to generate: the store registers the connector automatically on first access.",
|
|
31
|
+
"Confirm and click Connect: the store's sign-in screen opens — sign in with YOUR own owner account and authorise the access.",
|
|
32
|
+
"Done: enable the connector in the chat so the assistant can query and operate your store.",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* ChatGPT's two-stage configuration (Developer mode is now required before a
|
|
37
|
+
* connector can be created). Stage 1 enables Developer mode in Security & login;
|
|
38
|
+
* stage 2 creates the connector and signs in — which registers the connection on
|
|
39
|
+
* the store side, so no prompt needs to be pasted afterwards.
|
|
40
|
+
*/
|
|
41
|
+
function chatgptConfigureStages(
|
|
42
|
+
platformName: string,
|
|
43
|
+
): readonly AiHostConfigureStage[] {
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
id: "enable-dev-mode",
|
|
47
|
+
label: "enable developer mode",
|
|
48
|
+
link: {
|
|
49
|
+
url: "https://chatgpt.com/plugins#settings/Security",
|
|
50
|
+
label: "Open Security and login",
|
|
51
|
+
},
|
|
52
|
+
steps: [
|
|
53
|
+
"Turn on Developer mode under Settings › Security and login.",
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "configurar",
|
|
58
|
+
label: "configure",
|
|
59
|
+
link: {
|
|
60
|
+
url: "https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins",
|
|
61
|
+
label: "Create the connector",
|
|
62
|
+
},
|
|
63
|
+
steps: [
|
|
64
|
+
"That opens a popup to create a new plugin. Give it your store's name, and put the link you copied in the previous step in the MCP field.",
|
|
65
|
+
'Tick "I understand and want to continue" — OpenAI has not reviewed this MCP server; they warn that sites may try to steal your data or push the model into harmful actions, including destroying data.',
|
|
66
|
+
`Click "Sign in with ${platformName}" and sign in with your owner account to authorise access. That is it: the connection is registered automatically.`,
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The AI hosts a store owner can connect, in recommended order. Same OAuth flow
|
|
74
|
+
* everywhere (the host drives it) — only the menu path differs per app.
|
|
75
|
+
*
|
|
76
|
+
* A FUNCTION of the platform's name, because one step is not generic: the
|
|
77
|
+
* ChatGPT connector's consent screen shows an OAuth button labelled with
|
|
78
|
+
* whoever operates the server, and the owner is told which button to click. It
|
|
79
|
+
* used to name one particular STORE on one particular deployment — not even the
|
|
80
|
+
* product, a tenant of it — so every other adopter instructed its owners to
|
|
81
|
+
* click a button that does not exist.
|
|
82
|
+
*/
|
|
83
|
+
export function EN_US_AI_HOST_GUIDES(platformName: string): readonly AiHostGuide[] {
|
|
84
|
+
const chatgptStages = chatgptConfigureStages(platformName);
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
id: "claude",
|
|
88
|
+
label: "Claude.ai",
|
|
89
|
+
brand: "claude",
|
|
90
|
+
kind: "In the browser",
|
|
91
|
+
link: {
|
|
92
|
+
url: "https://claude.ai/new?modal=add-custom-connector#settings/customize-connectors",
|
|
93
|
+
label: "Open Claude's connectors",
|
|
94
|
+
},
|
|
95
|
+
docs: {
|
|
96
|
+
url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
|
|
97
|
+
label: "Anthropic's own documentation — custom connectors",
|
|
98
|
+
},
|
|
99
|
+
steps: [
|
|
100
|
+
"Click the button above (or go to Settings › Customize › Connectors) and choose Add custom connector.",
|
|
101
|
+
"Name the connector (your store's name works) and paste your store's MCP server URL (copy it above) into the URL field.",
|
|
102
|
+
...CONNECTOR_TAIL,
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "claude-desktop",
|
|
107
|
+
label: "Claude Desktop",
|
|
108
|
+
brand: "claude",
|
|
109
|
+
kind: "App (Windows/Mac)",
|
|
110
|
+
docs: {
|
|
111
|
+
url: "https://support.anthropic.com/en/articles/11175166-how-do-i-connect-mcp-servers-to-claude-ai",
|
|
112
|
+
label: "Anthropic's own documentation — custom connectors",
|
|
113
|
+
},
|
|
114
|
+
steps: [
|
|
115
|
+
"Open Claude Desktop and go to Settings (⚙️) › Connectors.",
|
|
116
|
+
"Click Add custom connector and paste your store's MCP server URL (copy it above).",
|
|
117
|
+
...CONNECTOR_TAIL,
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: "chatgpt",
|
|
122
|
+
label: "ChatGPT",
|
|
123
|
+
brand: "openai",
|
|
124
|
+
kind: "In the browser",
|
|
125
|
+
link: {
|
|
126
|
+
url: "https://chatgpt.com/plugins",
|
|
127
|
+
label: "Open ChatGPT's plugins",
|
|
128
|
+
},
|
|
129
|
+
docs: {
|
|
130
|
+
url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
|
|
131
|
+
label: "OpenAI's own documentation — connecting an MCP server to ChatGPT",
|
|
132
|
+
},
|
|
133
|
+
configureStages: chatgptStages,
|
|
134
|
+
// Mirrors the flattened stage instructions so the MCP connect guide
|
|
135
|
+
// (`connectToChatGpt`) can never drift from what owners see in the wizard.
|
|
136
|
+
steps: chatgptStages.flatMap((stage) => stage.steps),
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: "codex",
|
|
140
|
+
label: "Codex",
|
|
141
|
+
brand: "openai",
|
|
142
|
+
kind: "Developer app / CLI",
|
|
143
|
+
link: {
|
|
144
|
+
url: "https://developers.openai.com/codex",
|
|
145
|
+
label: "Codex documentation",
|
|
146
|
+
},
|
|
147
|
+
docs: {
|
|
148
|
+
url: "https://developers.openai.com/apps-sdk/deploy/connect-chatgpt",
|
|
149
|
+
label: "OpenAI's own documentation — connecting an MCP server",
|
|
150
|
+
},
|
|
151
|
+
steps: [
|
|
152
|
+
"In Codex, open the MCP settings (Settings › MCP in the app, or the config file on the CLI).",
|
|
153
|
+
"Add an MCP server and paste your store's MCP server URL (copy it above) as a remote (HTTP) connector.",
|
|
154
|
+
...CONNECTOR_TAIL,
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const EN_US_AI_CAPABILITIES: readonly AiCapability[] = [
|
|
161
|
+
// The `detail` of each is a QUESTION a reader could paste verbatim, so it is
|
|
162
|
+
// written as one rather than described.
|
|
163
|
+
{
|
|
164
|
+
id: "orders",
|
|
165
|
+
title: "Follow your orders",
|
|
166
|
+
detail: '"Which orders came in today?"',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: "inventory",
|
|
170
|
+
title: "Keep on top of stock",
|
|
171
|
+
detail: '"How much of product X is left? Record 20 units received."',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "catalog",
|
|
175
|
+
title: "Manage the catalog",
|
|
176
|
+
detail: "Create and edit products and categories by chatting.",
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "sales",
|
|
180
|
+
title: "Understand your sales",
|
|
181
|
+
detail: '"What was this week\'s revenue?"',
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The permission model in one line, shown prominently: the assistant acts AS
|
|
187
|
+
* the signed-in owner (auth-passthrough) — it can do exactly what the owner
|
|
188
|
+
* can, nothing more, and no extra credential/API key is ever created.
|
|
189
|
+
*/
|
|
190
|
+
export const EN_US_AI_PERMISSION_MODEL: AiPermissionModel =
|
|
191
|
+
"The assistant acts on your behalf with exactly your permissions: it can do what you can do in your store — and nothing beyond that. There is no key or extra credential to create; the authorisation uses your own login.";
|
|
192
|
+
|
|
193
|
+
/** The en-US paste-in prompt, built from the host's own tool names. */
|
|
194
|
+
export function EN_US_AI_CONNECT_PROMPT(spec: AiConnectPromptSpec): string {
|
|
195
|
+
// The TOOL NAMES are the host's own identifiers and are interpolated, never
|
|
196
|
+
// translated: the assistant has to call them by the name they are registered
|
|
197
|
+
// under, so a translated verb here is a prompt that does nothing.
|
|
198
|
+
return (
|
|
199
|
+
"You now have access to my store's MCP connector. Do the following, in order:\n" +
|
|
200
|
+
`1) Run the ${spec.announceTool} tool, saying which assistant you are (host: "chatgpt", "claude" or "codex"), to register the connection with my store.\n` +
|
|
201
|
+
`2) Run the ${spec.probeTool} tool to confirm access to ${spec.probeSubject}.\n` +
|
|
202
|
+
`If you need the store identifier, ask me for the ${spec.identifierName}.`
|
|
203
|
+
);
|
|
204
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -104,3 +104,15 @@ export {
|
|
|
104
104
|
PT_BR_AI_HOST_GUIDES,
|
|
105
105
|
PT_BR_AI_PERMISSION_MODEL,
|
|
106
106
|
} from "./pt-BR";
|
|
107
|
+
export {
|
|
108
|
+
EN_US_AI_CAPABILITIES,
|
|
109
|
+
EN_US_AI_CONNECT_PROMPT,
|
|
110
|
+
EN_US_AI_HOST_GUIDES,
|
|
111
|
+
EN_US_AI_PERMISSION_MODEL,
|
|
112
|
+
} from "./en-US";
|
|
113
|
+
export {
|
|
114
|
+
AI_CAPABILITIES,
|
|
115
|
+
AI_CONNECT_PROMPT,
|
|
116
|
+
AI_HOST_GUIDES,
|
|
117
|
+
AI_PERMISSION_MODEL,
|
|
118
|
+
} from "./locales";
|
package/src/locales.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EN_US_AI_CAPABILITIES,
|
|
3
|
+
EN_US_AI_CONNECT_PROMPT,
|
|
4
|
+
EN_US_AI_HOST_GUIDES,
|
|
5
|
+
EN_US_AI_PERMISSION_MODEL,
|
|
6
|
+
} from "./en-US";
|
|
7
|
+
import type {
|
|
8
|
+
AiCapability,
|
|
9
|
+
AiConnectPromptSpec,
|
|
10
|
+
AiHostGuide,
|
|
11
|
+
AiPermissionModel,
|
|
12
|
+
} from "./guide";
|
|
13
|
+
import {
|
|
14
|
+
PT_BR_AI_CAPABILITIES,
|
|
15
|
+
PT_BR_AI_CONNECT_PROMPT,
|
|
16
|
+
PT_BR_AI_HOST_GUIDES,
|
|
17
|
+
PT_BR_AI_PERMISSION_MODEL,
|
|
18
|
+
} from "./pt-BR";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The AI-integration surface in both languages, keyed by tag — what a host
|
|
22
|
+
* hands to `@12-apps/i18n` when the reader's language is a property of the
|
|
23
|
+
* request rather than of the deployment.
|
|
24
|
+
*
|
|
25
|
+
* Two of these are FUNCTIONS rather than tables, and stay so:
|
|
26
|
+
*
|
|
27
|
+
* - `AI_HOST_GUIDES` takes the platform's name, because the ChatGPT consent
|
|
28
|
+
* screen shows an OAuth button labelled with whoever operates the server and
|
|
29
|
+
* the owner is told which button to click. It once named one particular
|
|
30
|
+
* tenant of one deployment, so every other adopter instructed its owners to
|
|
31
|
+
* click a button that does not exist.
|
|
32
|
+
* - `AI_CONNECT_PROMPT` takes the host's own tool names, which the assistant
|
|
33
|
+
* must call by the name they are registered under.
|
|
34
|
+
*
|
|
35
|
+
* `LocalePack` is mirrored here rather than imported so the package stays
|
|
36
|
+
* liftable into a repo that has never heard of `@12-apps/i18n`.
|
|
37
|
+
*/
|
|
38
|
+
type LocalePack<T> = { readonly "pt-BR": T; readonly "en-US": T };
|
|
39
|
+
|
|
40
|
+
export const AI_HOST_GUIDES = {
|
|
41
|
+
"pt-BR": PT_BR_AI_HOST_GUIDES,
|
|
42
|
+
"en-US": EN_US_AI_HOST_GUIDES,
|
|
43
|
+
} as const satisfies LocalePack<(platformName: string) => readonly AiHostGuide[]>;
|
|
44
|
+
|
|
45
|
+
export const AI_CONNECT_PROMPT = {
|
|
46
|
+
"pt-BR": PT_BR_AI_CONNECT_PROMPT,
|
|
47
|
+
"en-US": EN_US_AI_CONNECT_PROMPT,
|
|
48
|
+
} as const satisfies LocalePack<(spec: AiConnectPromptSpec) => string>;
|
|
49
|
+
|
|
50
|
+
export const AI_CAPABILITIES = {
|
|
51
|
+
"pt-BR": PT_BR_AI_CAPABILITIES,
|
|
52
|
+
"en-US": EN_US_AI_CAPABILITIES,
|
|
53
|
+
} as const satisfies LocalePack<readonly AiCapability[]>;
|
|
54
|
+
|
|
55
|
+
export const AI_PERMISSION_MODEL = {
|
|
56
|
+
"pt-BR": PT_BR_AI_PERMISSION_MODEL,
|
|
57
|
+
"en-US": EN_US_AI_PERMISSION_MODEL,
|
|
58
|
+
} as const satisfies LocalePack<AiPermissionModel>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@12-apps/mcp/manifest` — the SHARED wiring manifest.
|
|
3
|
+
*
|
|
4
|
+
* Identity, the Prisma contribution (the three tables behind the
|
|
5
|
+
* authorization server) and the runtime inventory: `http` on the server.
|
|
6
|
+
*
|
|
7
|
+
* ON THE `db` DECLARATION — the reason this manifest exists at all.
|
|
8
|
+
*
|
|
9
|
+
* This package ships `prisma/mcp.prisma` and a migration beside it, and until
|
|
10
|
+
* now nothing said so in a form a host assembler could read. The origin
|
|
11
|
+
* host's assembler discovers partials in two steps: a package that carries
|
|
12
|
+
* `"wiring": { "db": ... }` in its package.json is taken at its word, and a
|
|
13
|
+
* package that carries nothing falls back to a STRUCTURAL scan — every
|
|
14
|
+
* `prisma/*.prisma` under the package root is treated as a partial. So three
|
|
15
|
+
* tables reach somebody's database because a `readdir` found them, not
|
|
16
|
+
* because this package said they should. `@12-apps/notifications`' manifest
|
|
17
|
+
* closed exactly this gap for its four models and the anti-pattern audit
|
|
18
|
+
* names it directly; declaring changes no assembler behaviour (the
|
|
19
|
+
* declaration is read where the scan used to run) and closes the one case
|
|
20
|
+
* where composition was happening by accident.
|
|
21
|
+
*
|
|
22
|
+
* The mirror is what makes the declaration reachable: host assemblers are
|
|
23
|
+
* plain Node reading `node_modules` and cannot execute this TypeScript, so
|
|
24
|
+
* the contribution is repeated under `package.json` `"wiring": { "db": … }`
|
|
25
|
+
* and `assertDbMirror` pins the two together in this package's own test run.
|
|
26
|
+
*
|
|
27
|
+
* `composed`, not `isolated`, and the choice is forced. An isolated stack
|
|
28
|
+
* needs models carrying no relation into host tables — true of the three
|
|
29
|
+
* here as SHIPPED (`user_id` and `user_email` are deliberately by-value
|
|
30
|
+
* scalars, see the partial's header) — but the host is invited to add the FK
|
|
31
|
+
* in its own migration, and the origin host's is `ON DELETE CASCADE`. A
|
|
32
|
+
* package cannot declare isolation for models whose adopters relate them
|
|
33
|
+
* into their own account tables.
|
|
34
|
+
*
|
|
35
|
+
* ## THE NARROWINGS, each deliberate
|
|
36
|
+
*
|
|
37
|
+
* - **No `mcp` capability.** This package IS the MCP runtime — the
|
|
38
|
+
* OpenAPI→tools generator, the registry, the JSON-RPC transport, the
|
|
39
|
+
* coverage gate. It advertises no tools of its own, and a manifest that
|
|
40
|
+
* declared any would be the runtime describing itself to itself.
|
|
41
|
+
* - **No `permissions`.** Authorization here is the OAuth scope set
|
|
42
|
+
* (`MCP_SUPPORTED_SCOPES`) plus whatever the host's own RBAC says about
|
|
43
|
+
* the proxied endpoint — the point of bearer passthrough is that an agent
|
|
44
|
+
* inherits the caller's permissions rather than holding its own. There is
|
|
45
|
+
* no id for this package to contribute.
|
|
46
|
+
* - **No `web` inventory**, though `./react` ships the whole AI-connect
|
|
47
|
+
* onboarding flow. A `surface` contribution is a `createWeb*` FACTORY —
|
|
48
|
+
* one config object in, an object of component types out, memoised once by
|
|
49
|
+
* the binder. `./react` has no such factory: it exports components a host
|
|
50
|
+
* mounts with its own props (`AiIntegrationOnboarding` takes the store,
|
|
51
|
+
* the endpoint URL and the live connection at the call site). Inventing a
|
|
52
|
+
* factory here to have something to declare would freeze a props table
|
|
53
|
+
* three hosts pass differently, which is the opposite of what a surface
|
|
54
|
+
* contribution is for. When the flow grows a real bound surface, the
|
|
55
|
+
* inventory grows with it.
|
|
56
|
+
* - **No `env`.** The signing-key variables (`DEFAULT_SIGNING_KEY_ENV`,
|
|
57
|
+
* `DEFAULT_SIGNING_KEY_ID_ENV`) and `trustedOriginsFromEnv` are NAMES this
|
|
58
|
+
* package exports for a host to read `process.env` with; the package reads
|
|
59
|
+
* nothing itself, and the names are overridable per call. Declaring them
|
|
60
|
+
* would oblige a host to answer for variables it may legitimately have
|
|
61
|
+
* spelled differently.
|
|
62
|
+
* - **No `e2e`.** This package packages no journeys.
|
|
63
|
+
* - **No `jobs`.** Nothing here sweeps: authorization codes are stateless
|
|
64
|
+
* signed blobs (the partial's header says so — there is no `oauth_codes`
|
|
65
|
+
* table and nothing to expire), and refresh-token revocation happens on
|
|
66
|
+
* the rotation path rather than on a clock.
|
|
67
|
+
*
|
|
68
|
+
* `@12-apps/wiring` is a TYPE-ONLY devDependency (the report-builder move):
|
|
69
|
+
* the manifest is a plain `satisfies`-checked value, and the producer
|
|
70
|
+
* factories' runtime assertions run in this package's own test suite.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
import type { PackageManifest } from "@12-apps/wiring";
|
|
74
|
+
|
|
75
|
+
export const mcpManifest = {
|
|
76
|
+
name: "@12-apps/mcp",
|
|
77
|
+
contract: 1,
|
|
78
|
+
db: { partial: "prisma/mcp.prisma", migrations: "prisma/migrations" },
|
|
79
|
+
/**
|
|
80
|
+
* A refused token grant, an unresolvable signing key or a rejected
|
|
81
|
+
* redirect URI files under `mcp` rather than under whichever host mounted
|
|
82
|
+
* the authorization server. Mandatory for runtime manifests since wiring
|
|
83
|
+
* 1.3.0, and this is the surface that most needs it: every failure here is
|
|
84
|
+
* a caller who cannot connect, reported to them as an opaque OAuth error
|
|
85
|
+
* code by specification.
|
|
86
|
+
*/
|
|
87
|
+
observability: { namespace: "mcp" },
|
|
88
|
+
server: ["http"],
|
|
89
|
+
} as const satisfies PackageManifest;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@12-apps/mcp/manifest/server` — the server capabilities.
|
|
3
|
+
*
|
|
4
|
+
* `http.create` wraps `createApiMcpOauth` in a WIRE VIEW, and the reason is
|
|
5
|
+
* the shape of an OAuth answer. `McpOauthRoute.handle` takes a Fetch
|
|
6
|
+
* `Request` and returns a Fetch `Response` — not a `{ status, body }` pair —
|
|
7
|
+
* because every endpoint here answers something the JSON pair cannot say: a
|
|
8
|
+
* 302 whose `Location` IS the payload, a form-encoded exchange answering
|
|
9
|
+
* RFC 6749 §5.1/§5.2 with its own cache headers, a JWKS with a
|
|
10
|
+
* `public, max-age=300`, an RFC 8414/9728 document. `create-api-mcp-oauth`'s
|
|
11
|
+
* own docstring says a wrapper would only break those. So the view answers
|
|
12
|
+
* the contract's RAW half (`{ response }`, wiring 1.9.0), which exists for
|
|
13
|
+
* exactly this, and the descriptors stay untouched.
|
|
14
|
+
*
|
|
15
|
+
* ## THE RAW REQUEST IS REQUIRED, and the view says so out loud
|
|
16
|
+
*
|
|
17
|
+
* The contract obliges an adapter to fill `params`/`query`/`body` and lets it
|
|
18
|
+
* fill `request`. Every handler here needs the whole request — the exact URL
|
|
19
|
+
* (redirect_uri echo, PKCE parameters, the `Host` an issuer is derived
|
|
20
|
+
* from), the form body byte-for-byte, the cookie header the session is read
|
|
21
|
+
* off. So a missing `request` is refused loudly at the first call rather
|
|
22
|
+
* than silently producing an authorization server that mints codes for the
|
|
23
|
+
* wrong origin. `@12-apps/storage`'s view takes the same posture for the
|
|
24
|
+
* same reason.
|
|
25
|
+
*
|
|
26
|
+
* ## THE MOUNT IS THE ORIGIN ROOT
|
|
27
|
+
*
|
|
28
|
+
* `McpOauthRoute.path` is absolute from the origin root — `.well-known/*`
|
|
29
|
+
* cannot live under a prefix (RFC 8615), and a connector reads those
|
|
30
|
+
* documents before it has ever spoken to us. The consumer joins
|
|
31
|
+
* `mountPath + path`, so the ONLY correct binding is `mountPath: "/"`; the
|
|
32
|
+
* paths themselves stay configurable per host through `config.paths`, which
|
|
33
|
+
* is where a host that serves `authorize` somewhere else says so. Binding
|
|
34
|
+
* this surface under a prefix would move the discovery documents off the
|
|
35
|
+
* two URLs the specification reserves, and the symptom is a connector that
|
|
36
|
+
* cannot find the authorization server at all.
|
|
37
|
+
*
|
|
38
|
+
* ## EVERY ROUTE IS `public`, and that is a decision
|
|
39
|
+
*
|
|
40
|
+
* Not "unguarded": these six ARE the authentication, so a host RBAC gate in
|
|
41
|
+
* front of them would demand a token from the endpoint that issues tokens.
|
|
42
|
+
* `authorize` reads the host's cookie session itself (`config.resolveSession`)
|
|
43
|
+
* and sends an anonymous caller through the host's sign-in flow; `token` and
|
|
44
|
+
* `register` authenticate the CLIENT per RFC; the three documents are public
|
|
45
|
+
* by specification. `public` is also the contract's only kind that forbids a
|
|
46
|
+
* `permission` while allowing the routes to be reached anonymously — which
|
|
47
|
+
* is precisely the property that has to hold here.
|
|
48
|
+
*
|
|
49
|
+
* `@12-apps/wiring` is a TYPE-ONLY devDependency — see `./index`.
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
import type { AnyServerManifest, WireRequest, WireRouteAnswer } from "@12-apps/wiring";
|
|
53
|
+
|
|
54
|
+
import {
|
|
55
|
+
createApiMcpOauth,
|
|
56
|
+
type ApiMcpOauth,
|
|
57
|
+
type McpOauthConfig,
|
|
58
|
+
type McpOauthRoute,
|
|
59
|
+
} from "../oauth";
|
|
60
|
+
|
|
61
|
+
/** One `McpOauthRoute` as the wiring contract reads it. */
|
|
62
|
+
function asWireRoute(route: McpOauthRoute): {
|
|
63
|
+
method: McpOauthRoute["method"];
|
|
64
|
+
path: string;
|
|
65
|
+
kind: "public";
|
|
66
|
+
handle(request: WireRequest): Promise<WireRouteAnswer>;
|
|
67
|
+
} {
|
|
68
|
+
return {
|
|
69
|
+
method: route.method,
|
|
70
|
+
path: route.path,
|
|
71
|
+
kind: "public",
|
|
72
|
+
handle: async (request) => {
|
|
73
|
+
if (!request.request) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
"@12-apps/mcp/oauth needs the raw request — bind an adapter that forwards it.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return { response: await route.handle(request.request) };
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** `createApiMcpOauth`, its routes re-shaped for the aggregate. */
|
|
84
|
+
export function createWireApiMcpOauth(
|
|
85
|
+
config: McpOauthConfig,
|
|
86
|
+
): Omit<ApiMcpOauth, "routes"> & { routes: ReturnType<typeof asWireRoute>[] } {
|
|
87
|
+
const api = createApiMcpOauth(config);
|
|
88
|
+
return { ...api, routes: api.routes.map(asWireRoute) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const mcpServerManifest = {
|
|
92
|
+
name: "@12-apps/mcp",
|
|
93
|
+
http: { create: createWireApiMcpOauth },
|
|
94
|
+
} as const satisfies AnyServerManifest;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { EN_US_CONFIRM_ACTION_COPY } from "@12-apps/ui/en-US";
|
|
2
|
+
|
|
3
|
+
import type { McpAiCopy } from "./copy";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The en-US pack for the AI-integration screens — a NAMED constant a host
|
|
7
|
+
* passes by hand, never a default.
|
|
8
|
+
*
|
|
9
|
+
* `statusBoard.confirmAction` composes `@12-apps/ui`'s own English pack rather
|
|
10
|
+
* than restating it, exactly as the pt-BR side composes the Portuguese one.
|
|
11
|
+
*
|
|
12
|
+
* The assistant NAMES (Claude, ChatGPT, Codex) are products, not words: they
|
|
13
|
+
* are spelled the same in every language, and `hostLabel` arrives as an
|
|
14
|
+
* argument so a sentence can place it where its own grammar wants.
|
|
15
|
+
*/
|
|
16
|
+
export const EN_US_MCP_AI_COPY: McpAiCopy = {
|
|
17
|
+
capabilities: {
|
|
18
|
+
heading: "What the assistant does for you",
|
|
19
|
+
subheading: "No spreadsheets, no clicking — just ask it in the chat.",
|
|
20
|
+
},
|
|
21
|
+
landing: {
|
|
22
|
+
eyebrow: "AI integration",
|
|
23
|
+
// Three fragments the heading renders around an emphasised middle, so the
|
|
24
|
+
// seam has to survive: "Connect / AI assistants / to your store".
|
|
25
|
+
titleLead: "Connect",
|
|
26
|
+
titleEmphasis: "AI assistants",
|
|
27
|
+
titleTail: "to your store",
|
|
28
|
+
lede:
|
|
29
|
+
"Let Claude, ChatGPT and other assistants answer questions about your menu, stock and " +
|
|
30
|
+
"orders — and take actions for you, right in the chat. Securely, with nothing to install.",
|
|
31
|
+
trust: [
|
|
32
|
+
// The `id`s are the package's own and are NOT words: the component keys
|
|
33
|
+
// its icons off them.
|
|
34
|
+
{
|
|
35
|
+
id: "login",
|
|
36
|
+
label: "Uses your own login",
|
|
37
|
+
caption: "No extra keys or credentials",
|
|
38
|
+
},
|
|
39
|
+
{ id: "install", label: "Nothing to install", caption: "Connects in minutes" },
|
|
40
|
+
{
|
|
41
|
+
id: "permissions",
|
|
42
|
+
label: "Only what you can do",
|
|
43
|
+
caption: "Your permissions, nothing beyond them",
|
|
44
|
+
},
|
|
45
|
+
{ id: "surface", label: "Browser or app", caption: "Claude, ChatGPT, Codex" },
|
|
46
|
+
],
|
|
47
|
+
start: "Get started",
|
|
48
|
+
},
|
|
49
|
+
flow: {
|
|
50
|
+
steps: {
|
|
51
|
+
select: "Choose",
|
|
52
|
+
copyUrl: "Copy URL",
|
|
53
|
+
configure: "Configure",
|
|
54
|
+
connect: "Connect",
|
|
55
|
+
install: "Install",
|
|
56
|
+
confirm: "Confirm",
|
|
57
|
+
},
|
|
58
|
+
back: "Back",
|
|
59
|
+
next: "Next",
|
|
60
|
+
advance: "Continue",
|
|
61
|
+
finish: "Finish",
|
|
62
|
+
copyUrl: "Copy URL",
|
|
63
|
+
copied: "Copied",
|
|
64
|
+
copyMessage: "Copy message",
|
|
65
|
+
copyUrlTitle: "Copy your store's URL",
|
|
66
|
+
urlCaption:
|
|
67
|
+
"It is the only thing you paste into the assistant — copying it moves you to the next step.",
|
|
68
|
+
promptCaption:
|
|
69
|
+
"That is how it connects, identifies itself (Claude, ChatGPT…) and how we record the connection.",
|
|
70
|
+
askTitle: "Ask the assistant to connect",
|
|
71
|
+
installBody:
|
|
72
|
+
"Open your store's plugin, click Install and authorise access — no URL to copy, no credentials to generate.",
|
|
73
|
+
installAction: "Install the store plugin",
|
|
74
|
+
pasteTitle: "Paste this message into the assistant",
|
|
75
|
+
pasteInstallCaption:
|
|
76
|
+
"Paste it into the assistant so it connects, identifies itself and confirms access.",
|
|
77
|
+
connectedTo: (hostLabel) => `${hostLabel} is connected to your store.`,
|
|
78
|
+
waitingTitle: "Waiting for the connection",
|
|
79
|
+
waitingBody: (hostLabel) =>
|
|
80
|
+
`As soon as you authorise access in ${hostLabel}, it appears here automatically.`,
|
|
81
|
+
testNow: "Test it now",
|
|
82
|
+
},
|
|
83
|
+
statusBoard: {
|
|
84
|
+
instructions: "Instructions",
|
|
85
|
+
connected: "Connected",
|
|
86
|
+
notConnected: "Not connected yet",
|
|
87
|
+
connect: "Connect",
|
|
88
|
+
disconnect: "Disconnect",
|
|
89
|
+
disconnectTitle: "Disconnect this assistant?",
|
|
90
|
+
disconnectBody:
|
|
91
|
+
"It loses access to your store immediately. To use it again you will have to connect it afresh.",
|
|
92
|
+
disconnectConfirm: "Disconnect",
|
|
93
|
+
disconnectError: "Could not disconnect. Try again.",
|
|
94
|
+
boardTitle: "Connected assistants",
|
|
95
|
+
boardCaption:
|
|
96
|
+
"Green are the ones already working with your store; red are the ones still to connect.",
|
|
97
|
+
confirmAction: EN_US_CONFIRM_ACTION_COPY,
|
|
98
|
+
},
|
|
99
|
+
summary: {
|
|
100
|
+
activeNow: "active now",
|
|
101
|
+
activeMinutes: (minutes) => `active ${minutes} min ago`,
|
|
102
|
+
activeHours: (hours) => `active ${hours} h ago`,
|
|
103
|
+
activeDays: (days) => `active ${days} days ago`,
|
|
104
|
+
configured: "Integration configured",
|
|
105
|
+
connectedSuffix: "Connected",
|
|
106
|
+
connectedSeveral: (names) => `${names} connected`,
|
|
107
|
+
connectedGeneric: "AI connected",
|
|
108
|
+
},
|
|
109
|
+
hostSelect: {
|
|
110
|
+
heading: "Choose your assistant",
|
|
111
|
+
caption: "Pick where you use AI — the walkthrough is tailored to it.",
|
|
112
|
+
},
|
|
113
|
+
connectGuide: {
|
|
114
|
+
urlLabel: "Your store's server URL",
|
|
115
|
+
urlHint: "Copy this URL and paste it into your assistant to connect it to the store.",
|
|
116
|
+
// Ends mid-sentence: the screen renders a documentation link straight after.
|
|
117
|
+
moreInfo: "For more, see the",
|
|
118
|
+
connectOn: (hostLabel) => `Connect in ${hostLabel}`,
|
|
119
|
+
},
|
|
120
|
+
onboarding: {
|
|
121
|
+
title: "Connect AI assistants to your store",
|
|
122
|
+
editLabel: "Connect AI",
|
|
123
|
+
collapseLabel: "Hide",
|
|
124
|
+
},
|
|
125
|
+
};
|
package/src/react/index.ts
CHANGED
|
@@ -47,6 +47,22 @@ export {
|
|
|
47
47
|
PT_BR_AI_PERMISSION_MODEL,
|
|
48
48
|
} from "../pt-BR";
|
|
49
49
|
|
|
50
|
+
// The English twins and the tag-keyed records, re-exported beside them so a
|
|
51
|
+
// screen reaching for the AI surface finds all of it on one subpath — which is
|
|
52
|
+
// how the pt-BR names have always been reachable from here.
|
|
53
|
+
export {
|
|
54
|
+
EN_US_AI_CAPABILITIES,
|
|
55
|
+
EN_US_AI_CONNECT_PROMPT,
|
|
56
|
+
EN_US_AI_HOST_GUIDES,
|
|
57
|
+
EN_US_AI_PERMISSION_MODEL,
|
|
58
|
+
} from "../en-US";
|
|
59
|
+
export {
|
|
60
|
+
AI_CAPABILITIES,
|
|
61
|
+
AI_CONNECT_PROMPT,
|
|
62
|
+
AI_HOST_GUIDES,
|
|
63
|
+
AI_PERMISSION_MODEL,
|
|
64
|
+
} from "../locales";
|
|
65
|
+
|
|
50
66
|
export type {
|
|
51
67
|
AiCapabilitiesCopy,
|
|
52
68
|
AiConnectGuideCopy,
|
|
@@ -60,3 +76,5 @@ export type {
|
|
|
60
76
|
McpAiCopy,
|
|
61
77
|
} from "./copy";
|
|
62
78
|
export { PT_BR_MCP_AI_COPY } from "./pt-BR";
|
|
79
|
+
export { EN_US_MCP_AI_COPY } from "./en-US";
|
|
80
|
+
export { MCP_AI_COPY } from "./locales";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpAiCopy } from "./copy";
|
|
2
|
+
import { EN_US_MCP_AI_COPY } from "./en-US";
|
|
3
|
+
import { PT_BR_MCP_AI_COPY } from "./pt-BR";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The AI-integration screens in both languages, keyed by tag.
|
|
7
|
+
*
|
|
8
|
+
* `LocalePack` is mirrored here rather than imported so the package stays
|
|
9
|
+
* liftable into a repo that has never heard of `@12-apps/i18n`. The named
|
|
10
|
+
* single-language packs stay exported and unchanged.
|
|
11
|
+
*/
|
|
12
|
+
type LocalePack<T> = { readonly "pt-BR": T; readonly "en-US": T };
|
|
13
|
+
|
|
14
|
+
export const MCP_AI_COPY = {
|
|
15
|
+
"pt-BR": PT_BR_MCP_AI_COPY,
|
|
16
|
+
"en-US": EN_US_MCP_AI_COPY,
|
|
17
|
+
} as const satisfies LocalePack<McpAiCopy>;
|