@12-apps/mcp 3.15.0 → 3.17.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 +41 -0
- package/README.md +1 -1
- package/dist/{chunk-VDD4YRNP.js → chunk-EANHJLDH.js} +13 -4
- package/dist/chunk-EANHJLDH.js.map +1 -0
- package/dist/chunk-F3LMK6OL.js +25 -0
- package/dist/chunk-F3LMK6OL.js.map +1 -0
- package/dist/{chunk-UIILEGAC.js → chunk-WCZC4TPX.js} +193 -48
- package/dist/chunk-WCZC4TPX.js.map +1 -0
- package/dist/{create-api-mcp-oauth-CsC0jlH7.d.ts → create-api-mcp-oauth-BEvYLRBV.d.ts} +96 -4
- package/dist/e2e/index.d.ts +109 -0
- package/dist/e2e/index.js +27 -0
- package/dist/e2e/index.js.map +1 -0
- package/dist/e2e/steps/journey.steps.d.ts +2 -0
- package/dist/e2e/steps/journey.steps.js +79 -0
- package/dist/e2e/steps/journey.steps.js.map +1 -0
- package/dist/{guide-KQNcXlMG.d.ts → guide-CrzdsdNf.d.ts} +1 -1
- package/dist/hono/index.d.ts +1 -1
- package/dist/hono/index.js +1 -1
- package/dist/index.d.ts +102 -4
- package/dist/index.js +71 -6
- package/dist/index.js.map +1 -1
- package/dist/{locales-eKE_OJw4.d.ts → locales-Cv0Pecvu.d.ts} +1 -1
- package/dist/manifest/index.d.ts +29 -7
- package/dist/manifest/index.js +2 -1
- package/dist/manifest/index.js.map +1 -1
- package/dist/manifest/server.d.ts +1 -1
- package/dist/manifest/server.js +2 -2
- package/dist/oauth/index.d.ts +19 -4
- package/dist/oauth/index.js +4 -2
- package/dist/react/index.d.ts +3 -3
- package/features/ai-connect.feature +46 -0
- package/package.json +25 -8
- package/prisma/mcp.prisma +10 -0
- package/prisma/migrations/20260910120000_add_refresh_grace_seal/migration.sql +37 -0
- package/src/e2e/globs.ts +70 -0
- package/src/e2e/index.ts +16 -0
- package/src/e2e/steps/journey.steps.ts +136 -0
- package/src/e2e/world.ts +84 -0
- package/src/index.ts +11 -0
- package/src/manifest/index.ts +24 -7
- package/src/oauth/access-token.ts +72 -10
- package/src/oauth/context.ts +20 -0
- package/src/oauth/index.ts +2 -0
- package/src/oauth/prisma-stores.ts +16 -5
- package/src/oauth/refresh-lineage.ts +77 -0
- package/src/oauth/refresh.ts +169 -82
- package/src/oauth/rotation-grace.ts +216 -0
- package/src/oauth/stores.ts +45 -1
- package/src/oauth/token-grants.ts +4 -1
- package/src/server/auth-failure.ts +145 -0
- package/src/server/jsonrpc.ts +44 -5
- package/dist/chunk-UIILEGAC.js.map +0 -1
- package/dist/chunk-VDD4YRNP.js.map +0 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/** Every packaged `.feature`, for `defineBddConfig({ features })`. */
|
|
4
|
+
declare const mcpFeatures: string;
|
|
5
|
+
/**
|
|
6
|
+
* The base `defineBddConfig({ featuresRoot })` must be given, and it is NOT
|
|
7
|
+
* optional decoration.
|
|
8
|
+
*
|
|
9
|
+
* bddgen mirrors each feature's path RELATIVE TO `featuresRoot` under
|
|
10
|
+
* `outputDir`. Left unset it defaults to the config's own directory, so a
|
|
11
|
+
* feature living under `node_modules/...` compiles to a path Playwright then
|
|
12
|
+
* IGNORES, because its default `testIgnore` excludes `**\/node_modules/**`.
|
|
13
|
+
*
|
|
14
|
+
* The result is the worst kind of green: bddgen reports the features compiled,
|
|
15
|
+
* Playwright collects zero specs from them, and the run passes with the whole
|
|
16
|
+
* packaged suite silently absent.
|
|
17
|
+
*
|
|
18
|
+
* A host running SEVERAL packaged suites gives `featuresRoot` the directory they
|
|
19
|
+
* all sit under, since bddgen takes exactly one — and that is safe to do,
|
|
20
|
+
* because a feature outside it is a hard exit from bddgen, never a quiet
|
|
21
|
+
* omission.
|
|
22
|
+
*/
|
|
23
|
+
declare const mcpFeaturesRoot: string;
|
|
24
|
+
/**
|
|
25
|
+
* Every packaged step definition, for `defineBddConfig({ steps })`.
|
|
26
|
+
*
|
|
27
|
+
* COMPILED JavaScript, and that is the reason this package has a build step at
|
|
28
|
+
* all while everything else it exports is raw `.ts` through the `exports` map.
|
|
29
|
+
* Those entries are consumed by an application's BUNDLER, which transpiles
|
|
30
|
+
* whatever it is pointed at. These are loaded by NODE — `playwright.config.ts`
|
|
31
|
+
* imports this module, and bddgen imports the step files — and Node refuses to
|
|
32
|
+
* strip types from anything under `node_modules`
|
|
33
|
+
* (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`). Playwright's own TS transform
|
|
34
|
+
* does not rescue them either; it skips `node_modules` by design.
|
|
35
|
+
*
|
|
36
|
+
* The host's own steps glob must ALSO be listed — that is where its
|
|
37
|
+
* `defineMcpConnectWorld` call lives, and playwright-bdd imports every step
|
|
38
|
+
* file before any scenario runs, which is what makes the registration land in
|
|
39
|
+
* time in every worker.
|
|
40
|
+
*/
|
|
41
|
+
declare const mcpSteps: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The port a HOST implements to run the packaged AI-connect journeys.
|
|
45
|
+
*
|
|
46
|
+
* These journeys were written because the walkthrough they cover was tested
|
|
47
|
+
* NOWHERE. `./react` ships the whole flow — the landing, the assistant picker,
|
|
48
|
+
* the endpoint to copy, the configure and connect steps, the confirmation and
|
|
49
|
+
* its re-test — and twenty test ids go with it. Not one of them appeared in
|
|
50
|
+
* this package's own suite, and not one appeared in the origin host's specs
|
|
51
|
+
* either: that host's `ai.e2e.ts` drives its OWN plan lock and upsell modal,
|
|
52
|
+
* reaching only `ai-onboarding` and the status board on the way past.
|
|
53
|
+
*
|
|
54
|
+
* So the flow a store owner actually walks was covered by nothing, in either
|
|
55
|
+
* repo. That is the gap the `e2e` capability exists to convert into a
|
|
56
|
+
* declaration rather than an omission nobody can see.
|
|
57
|
+
*
|
|
58
|
+
* What stays the host's: how it signs an owner in, where it mounts the flow,
|
|
59
|
+
* and which assistant its `hosts` config offers — the guides are REQUIRED
|
|
60
|
+
* config (FUT-760), so the package has no assistant of its own to name.
|
|
61
|
+
*/
|
|
62
|
+
/** Facts about the host that the assertions have to name. */
|
|
63
|
+
interface McpConnectFixtures {
|
|
64
|
+
/**
|
|
65
|
+
* The id of an assistant the host offers whose guide has NO `pluginUrl`.
|
|
66
|
+
*
|
|
67
|
+
* The flow BRANCHES on that field: a guide carrying one gets `InstallStep`
|
|
68
|
+
* (a single "open the install link" button), and a guide without one gets
|
|
69
|
+
* the three-step manual path — copy the endpoint, configure, connect. These
|
|
70
|
+
* journeys walk the MANUAL path, so the host has to point at a guide that
|
|
71
|
+
* takes it. Naming the branch here rather than guessing keeps the scenario
|
|
72
|
+
* from failing in a host whose first assistant happens to ship a plugin.
|
|
73
|
+
*/
|
|
74
|
+
manualHostId: string;
|
|
75
|
+
/** The endpoint URL that host serves, as it is rendered for copying. */
|
|
76
|
+
endpointUrl: string;
|
|
77
|
+
}
|
|
78
|
+
/** What a host must be able to do for these journeys to run in it. */
|
|
79
|
+
interface McpConnectWorld {
|
|
80
|
+
/**
|
|
81
|
+
* Put the browser in a known signed-in state as somebody who may connect an
|
|
82
|
+
* assistant, with the flow's progress RESET to its first run.
|
|
83
|
+
*
|
|
84
|
+
* The reset is load-bearing rather than hygiene: the wizard persists its step
|
|
85
|
+
* through `@12-apps/onboarding`, so a scenario that advanced it would hand
|
|
86
|
+
* the next one a flow resuming from the middle — and the landing step, which
|
|
87
|
+
* two of these scenarios assert, would never render.
|
|
88
|
+
*/
|
|
89
|
+
signInAsOwner(page: Page): Promise<void>;
|
|
90
|
+
/** Land on the screen that mounts `AiIntegrationOnboarding`. */
|
|
91
|
+
openAiIntegrationScreen(page: Page): Promise<void>;
|
|
92
|
+
fixtures: McpConnectFixtures;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Install the host's implementation. Call this from a module inside the host's
|
|
96
|
+
* OWN steps glob — playwright-bdd imports every step file before any scenario
|
|
97
|
+
* runs, so a top-level call there is registered in time, in every worker.
|
|
98
|
+
*/
|
|
99
|
+
declare function defineMcpConnectWorld(world: McpConnectWorld): void;
|
|
100
|
+
/**
|
|
101
|
+
* The installed world, or a refusal naming the fix.
|
|
102
|
+
*
|
|
103
|
+
* Throws rather than returning null: a step that ran against an absent world
|
|
104
|
+
* would fail on whatever it touched next, somewhere unrelated to the actual
|
|
105
|
+
* mistake, which is a diagnosis nobody should have to make twice.
|
|
106
|
+
*/
|
|
107
|
+
declare function mcpConnectWorld(): McpConnectWorld;
|
|
108
|
+
|
|
109
|
+
export { type McpConnectFixtures, type McpConnectWorld, defineMcpConnectWorld, mcpConnectWorld, mcpFeatures, mcpFeaturesRoot, mcpSteps };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineMcpConnectWorld,
|
|
3
|
+
mcpConnectWorld
|
|
4
|
+
} from "../chunk-F3LMK6OL.js";
|
|
5
|
+
import {
|
|
6
|
+
__name
|
|
7
|
+
} from "../chunk-7QVYU63E.js";
|
|
8
|
+
|
|
9
|
+
// src/e2e/globs.ts
|
|
10
|
+
import { createRequire } from "module";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
var require_ = createRequire(import.meta.url);
|
|
13
|
+
function packageRoot() {
|
|
14
|
+
return dirname(require_.resolve("@12-apps/mcp/package.json"));
|
|
15
|
+
}
|
|
16
|
+
__name(packageRoot, "packageRoot");
|
|
17
|
+
var mcpFeatures = join(packageRoot(), "features/**/*.feature");
|
|
18
|
+
var mcpFeaturesRoot = join(packageRoot(), "features");
|
|
19
|
+
var mcpSteps = join(packageRoot(), "dist/e2e/steps/**/*.js");
|
|
20
|
+
export {
|
|
21
|
+
defineMcpConnectWorld,
|
|
22
|
+
mcpConnectWorld,
|
|
23
|
+
mcpFeatures,
|
|
24
|
+
mcpFeaturesRoot,
|
|
25
|
+
mcpSteps
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/e2e/globs.ts"],"sourcesContent":["import { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\n\n/**\n * Where this package's journeys and their steps live, ON DISK, in the consumer's\n * `node_modules`.\n *\n * `defineBddConfig` takes filesystem globs, not module specifiers — it hands\n * them to a glob matcher, so `'@12-apps/mcp/features/**'` matches\n * nothing and, worse, matches nothing SILENTLY: bddgen compiles the features it\n * found, finds none, and the run is green with zero journeys.\n *\n * So the paths are RESOLVED here instead of written down by every consumer. A\n * host that hard-codes `node_modules/@12-apps/mcp/...` is broken by\n * pnpm's nested store, by a workspace link, and by this package's own layout\n * changing; resolving from the package's own entry point is correct under all\n * three.\n */\nconst require_ = createRequire(import.meta.url);\n\n/**\n * The package root, found from a file this package definitely exports.\n * `./package.json` is exported precisely so this lookup needs no guess about\n * directory depth.\n */\nfunction packageRoot(): string {\n return dirname(require_.resolve('@12-apps/mcp/package.json'));\n}\n\n/** Every packaged `.feature`, for `defineBddConfig({ features })`. */\nexport const mcpFeatures: string = join(packageRoot(), 'features/**/*.feature');\n\n/**\n * The base `defineBddConfig({ featuresRoot })` must be given, and it is NOT\n * optional decoration.\n *\n * bddgen mirrors each feature's path RELATIVE TO `featuresRoot` under\n * `outputDir`. Left unset it defaults to the config's own directory, so a\n * feature living under `node_modules/...` compiles to a path Playwright then\n * IGNORES, because its default `testIgnore` excludes `**\\/node_modules/**`.\n *\n * The result is the worst kind of green: bddgen reports the features compiled,\n * Playwright collects zero specs from them, and the run passes with the whole\n * packaged suite silently absent.\n *\n * A host running SEVERAL packaged suites gives `featuresRoot` the directory they\n * all sit under, since bddgen takes exactly one — and that is safe to do,\n * because a feature outside it is a hard exit from bddgen, never a quiet\n * omission.\n */\nexport const mcpFeaturesRoot: string = join(packageRoot(), 'features');\n\n/**\n * Every packaged step definition, for `defineBddConfig({ steps })`.\n *\n * COMPILED JavaScript, and that is the reason this package has a build step at\n * all while everything else it exports is raw `.ts` through the `exports` map.\n * Those entries are consumed by an application's BUNDLER, which transpiles\n * whatever it is pointed at. These are loaded by NODE — `playwright.config.ts`\n * imports this module, and bddgen imports the step files — and Node refuses to\n * strip types from anything under `node_modules`\n * (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`). Playwright's own TS transform\n * does not rescue them either; it skips `node_modules` by design.\n *\n * The host's own steps glob must ALSO be listed — that is where its\n * `defineMcpConnectWorld` call lives, and playwright-bdd imports every step\n * file before any scenario runs, which is what makes the registration land in\n * time in every worker.\n */\nexport const mcpSteps: string = join(packageRoot(), 'dist/e2e/steps/**/*.js');\n"],"mappings":";;;;;;;;;AAAA,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAiB9B,IAAM,WAAW,cAAc,YAAY,GAAG;AAO9C,SAAS,cAAsB;AAC7B,SAAO,QAAQ,SAAS,QAAQ,2BAA2B,CAAC;AAC9D;AAFS;AAKF,IAAM,cAAsB,KAAK,YAAY,GAAG,uBAAuB;AAoBvE,IAAM,kBAA0B,KAAK,YAAY,GAAG,UAAU;AAmB9D,IAAM,WAAmB,KAAK,YAAY,GAAG,wBAAwB;","names":[]}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mcpConnectWorld
|
|
3
|
+
} from "../../chunk-F3LMK6OL.js";
|
|
4
|
+
import {
|
|
5
|
+
__name
|
|
6
|
+
} from "../../chunk-7QVYU63E.js";
|
|
7
|
+
|
|
8
|
+
// src/e2e/steps/journey.steps.ts
|
|
9
|
+
import { expect } from "@playwright/test";
|
|
10
|
+
import { createBdd } from "playwright-bdd";
|
|
11
|
+
var { Given, When, Then } = createBdd();
|
|
12
|
+
Given("I am signed in as somebody who may connect an assistant", async ({ page }) => {
|
|
13
|
+
await mcpConnectWorld().signInAsOwner(page);
|
|
14
|
+
});
|
|
15
|
+
When("I open the AI integration screen", async ({ page }) => {
|
|
16
|
+
await mcpConnectWorld().openAiIntegrationScreen(page);
|
|
17
|
+
await expect(page.getByTestId("ai-onboarding")).toBeVisible();
|
|
18
|
+
});
|
|
19
|
+
Then("the landing explains the permission model before anything is connected", async ({ page }) => {
|
|
20
|
+
await expect(page.getByTestId("ai-landing")).toBeVisible();
|
|
21
|
+
await expect(page.getByTestId("ai-permission-callout")).toBeVisible();
|
|
22
|
+
});
|
|
23
|
+
Then("it shows examples of what an assistant can be asked", async ({ page }) => {
|
|
24
|
+
await expect(page.getByTestId("ai-capability-examples")).toBeVisible();
|
|
25
|
+
});
|
|
26
|
+
When("I start the walkthrough", async ({ page }) => {
|
|
27
|
+
await page.getByTestId("ai-landing-start").click();
|
|
28
|
+
await expect(page.getByTestId("ai-host-select")).toBeVisible();
|
|
29
|
+
});
|
|
30
|
+
When("I choose an assistant that has no one-click install", async ({ page }) => {
|
|
31
|
+
await page.getByTestId(`ai-host-card-${mcpConnectWorld().fixtures.manualHostId}`).click();
|
|
32
|
+
});
|
|
33
|
+
Then("I am asked to copy the store's endpoint", async ({ page }) => {
|
|
34
|
+
await expect(page.getByTestId("ai-copy-step")).toBeVisible();
|
|
35
|
+
await expect(page.getByTestId("mcp-endpoint-url")).toContainText(
|
|
36
|
+
mcpConnectWorld().fixtures.endpointUrl
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
async function copyEndpoint(page) {
|
|
40
|
+
await page.getByTestId("ai-copy-endpoint").click();
|
|
41
|
+
}
|
|
42
|
+
__name(copyEndpoint, "copyEndpoint");
|
|
43
|
+
async function openConnectorPage(page) {
|
|
44
|
+
await page.getByTestId(`ai-host-link-${mcpConnectWorld().fixtures.manualHostId}`).click();
|
|
45
|
+
}
|
|
46
|
+
__name(openConnectorPage, "openConnectorPage");
|
|
47
|
+
When("I copy the endpoint", async ({ page }) => {
|
|
48
|
+
await copyEndpoint(page);
|
|
49
|
+
});
|
|
50
|
+
Then("the walkthrough has moved on to configuring the connector", async ({ page }) => {
|
|
51
|
+
await expect(page.getByTestId("ai-configure-step")).toBeVisible();
|
|
52
|
+
await expect(page.getByTestId("ai-copy-step")).toHaveCount(0);
|
|
53
|
+
});
|
|
54
|
+
Then("continuing is refused until I open the connector page", async ({ page }) => {
|
|
55
|
+
await expect(page.getByTestId("ai-configure-next")).toBeDisabled();
|
|
56
|
+
});
|
|
57
|
+
Then("once opened, continuing reaches the connect step", async ({ page }) => {
|
|
58
|
+
await openConnectorPage(page);
|
|
59
|
+
await expect(page.getByTestId("ai-configure-next")).toBeEnabled();
|
|
60
|
+
await page.getByTestId("ai-configure-next").click();
|
|
61
|
+
await expect(page.getByTestId("ai-connect-step")).toBeVisible();
|
|
62
|
+
});
|
|
63
|
+
When("I go back a step", async ({ page }) => {
|
|
64
|
+
await page.getByTestId("ai-step-back").click();
|
|
65
|
+
});
|
|
66
|
+
When("I work through configuring and connecting", async ({ page }) => {
|
|
67
|
+
await openConnectorPage(page);
|
|
68
|
+
await page.getByTestId("ai-configure-next").click();
|
|
69
|
+
await expect(page.getByTestId("ai-connect-step")).toBeVisible();
|
|
70
|
+
await page.getByTestId("ai-connect-done").click();
|
|
71
|
+
});
|
|
72
|
+
Then("the confirmation is still waiting for the assistant", async ({ page }) => {
|
|
73
|
+
await expect(page.getByTestId("ai-confirm-waiting")).toBeVisible();
|
|
74
|
+
await expect(page.getByTestId("ai-confirm-connected")).toHaveCount(0);
|
|
75
|
+
});
|
|
76
|
+
Then("it offers to test the connection again", async ({ page }) => {
|
|
77
|
+
await expect(page.getByTestId("ai-confirm-retest")).toBeVisible();
|
|
78
|
+
});
|
|
79
|
+
//# sourceMappingURL=journey.steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/e2e/steps/journey.steps.ts"],"sourcesContent":["import { expect, type Page } from '@playwright/test';\nimport { createBdd } from 'playwright-bdd';\n\nimport { mcpConnectWorld } from '../world.js';\n\n/**\n * The packaged AI-connect journeys' step definitions.\n *\n * Every locator is a test id THIS package's own components render —\n * `ai-landing`, `ai-host-select`, `ai-copy-step`, `ai-configure-step`,\n * `ai-connect-step`, `ai-confirm-waiting`, `mcp-endpoint-url` — which is what\n * makes the scenarios portable: they mean the same thing in any app mounting\n * `AiIntegrationOnboarding`. Everything a host owns (sign-in, where the flow is\n * mounted, which assistants it offers) arrives through the world port.\n *\n * ## Why `package.json` declares this file under `sideEffects`\n *\n * Every Given/When/Then below runs at IMPORT time — registering a step is the\n * whole point of the module, and it exports nothing anybody imports by name. To\n * a bundler doing tree-shaking that reads as dead weight, and dropping it is\n * licensed: the result is a suite where bddgen reports the features compiled\n * and every scenario then fails on an undefined step.\n *\n * Not one step asserts a SENTENCE. The assistants' names, the button words and\n * the permission prose are REQUIRED host config (FUT-760) — this package ships\n * no assistant and no copy of its own — so a spec written against them could\n * only ever have run in one adopter.\n */\nconst { Given, When, Then } = createBdd();\n\nGiven('I am signed in as somebody who may connect an assistant', async ({ page }) => {\n await mcpConnectWorld().signInAsOwner(page);\n});\n\nWhen('I open the AI integration screen', async ({ page }) => {\n await mcpConnectWorld().openAiIntegrationScreen(page);\n await expect(page.getByTestId('ai-onboarding')).toBeVisible();\n});\n\nThen('the landing explains the permission model before anything is connected', async ({ page }) => {\n await expect(page.getByTestId('ai-landing')).toBeVisible();\n // The callout is the point of the landing: an owner is about to hand a third\n // party a key to their store, and this is the screen that says what the key\n // opens. It renders BEFORE any assistant is chosen, which is the only moment\n // the answer is still \"nothing\".\n await expect(page.getByTestId('ai-permission-callout')).toBeVisible();\n});\n\nThen('it shows examples of what an assistant can be asked', async ({ page }) => {\n await expect(page.getByTestId('ai-capability-examples')).toBeVisible();\n});\n\nWhen('I start the walkthrough', async ({ page }) => {\n await page.getByTestId('ai-landing-start').click();\n await expect(page.getByTestId('ai-host-select')).toBeVisible();\n});\n\nWhen('I choose an assistant that has no one-click install', async ({ page }) => {\n // The flow BRANCHES on the guide's `pluginUrl`: with one it becomes\n // Escolher → Instalar → Confirmar and there is no endpoint to copy at all.\n // The host names a guide that takes the manual path, because which\n // assistants exist is its configuration and not this package's.\n await page.getByTestId(`ai-host-card-${mcpConnectWorld().fixtures.manualHostId}`).click();\n});\n\nThen(\"I am asked to copy the store's endpoint\", async ({ page }) => {\n await expect(page.getByTestId('ai-copy-step')).toBeVisible();\n await expect(page.getByTestId('mcp-endpoint-url')).toContainText(\n mcpConnectWorld().fixtures.endpointUrl,\n );\n});\n\n/** Copying is the ACTION that advances this step — there is no next button. */\nasync function copyEndpoint(page: Page): Promise<void> {\n await page.getByTestId('ai-copy-endpoint').click();\n}\n\n/**\n * Open the assistant's connector page — the one action that unlocks \"next\".\n *\n * By its OWN id, not `getByRole('link')`: this control is a Button calling\n * `window.open`, and the only real `<a>` in the step is the DOCS link, which\n * deliberately does not unlock anything. A role query would have taken the\n * docs link and then failed on an assertion about the button.\n */\nasync function openConnectorPage(page: Page): Promise<void> {\n await page.getByTestId(`ai-host-link-${mcpConnectWorld().fixtures.manualHostId}`).click();\n}\n\nWhen('I copy the endpoint', async ({ page }) => {\n await copyEndpoint(page);\n});\n\nThen('the walkthrough has moved on to configuring the connector', async ({ page }) => {\n // The claim is that COPYING advanced it. The copy step offers no \"next\", so\n // reaching the configure step at all proves the copy handler drove the\n // wizard rather than some button the operator happened to press.\n await expect(page.getByTestId('ai-configure-step')).toBeVisible();\n await expect(page.getByTestId('ai-copy-step')).toHaveCount(0);\n});\n\nThen('continuing is refused until I open the connector page', async ({ page }) => {\n // The guard that stops an owner walking past the one step that does the\n // actual work: the connector page has to be opened before \"next\" unlocks.\n await expect(page.getByTestId('ai-configure-next')).toBeDisabled();\n});\n\nThen('once opened, continuing reaches the connect step', async ({ page }) => {\n await openConnectorPage(page);\n await expect(page.getByTestId('ai-configure-next')).toBeEnabled();\n await page.getByTestId('ai-configure-next').click();\n await expect(page.getByTestId('ai-connect-step')).toBeVisible();\n});\n\nWhen('I go back a step', async ({ page }) => {\n await page.getByTestId('ai-step-back').click();\n});\n\nWhen('I work through configuring and connecting', async ({ page }) => {\n await openConnectorPage(page);\n await page.getByTestId('ai-configure-next').click();\n await expect(page.getByTestId('ai-connect-step')).toBeVisible();\n await page.getByTestId('ai-connect-done').click();\n});\n\nThen('the confirmation is still waiting for the assistant', async ({ page }) => {\n // Waiting rather than connected, because nothing has actually connected: the\n // wizard reaching its last step is not evidence of a live connection, and\n // this is the step that refuses to claim otherwise.\n await expect(page.getByTestId('ai-confirm-waiting')).toBeVisible();\n await expect(page.getByTestId('ai-confirm-connected')).toHaveCount(0);\n});\n\nThen('it offers to test the connection again', async ({ page }) => {\n await expect(page.getByTestId('ai-confirm-retest')).toBeVisible();\n});\n"],"mappings":"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","names":[]}
|
|
@@ -147,4 +147,4 @@ interface AiConnectPromptCopy {
|
|
|
147
147
|
*/
|
|
148
148
|
declare function aiConnectPrompt(spec: AiConnectPromptSpec, copy: AiConnectPromptCopy): string;
|
|
149
149
|
|
|
150
|
-
export { type
|
|
150
|
+
export { type AiConnectPromptSpec as A, type AiHostGuide as a, type AiCapability as b, type AiPermissionModel as c, type AiProvider as d, type AiConnectPromptCopy as e, type AiHostBrand as f, type AiHostConfigureStage as g, type AiHostLink as h, aiConnectPrompt as i, providerForHostId as p };
|
package/dist/hono/index.d.ts
CHANGED
package/dist/hono/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { J as JsonSchema, a as ToolAnnotations, G as GeneratedTool, D as DispatchConfig, b as DispatchResult, R as RequestAuth, T as ToolManifest } from './generate-BCDqBUjZ.js';
|
|
2
2
|
export { A as AuthResolver, c as GenerateOptions, O as OpenApiDocument, d as OpenApiOperation, e as OpenApiParameter, f as OpenApiRequestBody, g as OpenApiResponse, P as ParameterLocation, h as ToolParameter, i as generateTools } from './generate-BCDqBUjZ.js';
|
|
3
|
-
export {
|
|
3
|
+
export { b as AiCapability, e as AiConnectPromptCopy, A as AiConnectPromptSpec, f as AiHostBrand, g as AiHostConfigureStage, a as AiHostGuide, h as AiHostLink, d as AiProvider, i as aiConnectPrompt, p as providerForHostId } from './guide-CrzdsdNf.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
export { A as AI_CAPABILITIES, a as AI_CONNECT_PROMPT, b as AI_HOST_GUIDES, c as AI_PERMISSION_MODEL, E as EN_US_AI_CAPABILITIES, d as EN_US_AI_CONNECT_PROMPT, e as EN_US_AI_HOST_GUIDES, f as EN_US_AI_PERMISSION_MODEL, P as PT_BR_AI_CAPABILITIES, g as PT_BR_AI_CONNECT_PROMPT, h as PT_BR_AI_HOST_GUIDES, i as PT_BR_AI_PERMISSION_MODEL } from './locales-
|
|
5
|
+
export { A as AI_CAPABILITIES, a as AI_CONNECT_PROMPT, b as AI_HOST_GUIDES, c as AI_PERMISSION_MODEL, E as EN_US_AI_CAPABILITIES, d as EN_US_AI_CONNECT_PROMPT, e as EN_US_AI_HOST_GUIDES, f as EN_US_AI_PERMISSION_MODEL, P as PT_BR_AI_CAPABILITIES, g as PT_BR_AI_CONNECT_PROMPT, h as PT_BR_AI_HOST_GUIDES, i as PT_BR_AI_PERMISSION_MODEL } from './locales-Cv0Pecvu.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Raised when a JSON Schema cannot be turned into a flat, self-contained tool
|
|
@@ -275,6 +275,93 @@ interface RegistryOptions {
|
|
|
275
275
|
}
|
|
276
276
|
declare function createToolRegistry(options: RegistryOptions): ToolRegistry;
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* What an unauthorized MCP call is TOLD, as opposed to what it is refused with.
|
|
280
|
+
*
|
|
281
|
+
* ## The failure this module exists to remove
|
|
282
|
+
*
|
|
283
|
+
* A resource server has three RFC 6750 challenge codes and a boolean's worth of
|
|
284
|
+
* expressiveness, so every way a bearer can fail arrives at the agent as one
|
|
285
|
+
* opaque refusal. `Authentication required` is what a lapsed connection, a token
|
|
286
|
+
* minted for a different origin, a surface an operator never switched on, and a
|
|
287
|
+
* missing scope all look like — identical, and none of them actionable.
|
|
288
|
+
*
|
|
289
|
+
* The cost is not cosmetic. An agent that cannot tell those apart cannot tell the
|
|
290
|
+
* user anything useful either: every tool call fails, the connector still reports
|
|
291
|
+
* itself connected, and the one thing that would fix the common case — reconnect
|
|
292
|
+
* it — is the one thing nobody is told to do. Reported from a live deployment as
|
|
293
|
+
* "every tool call failing, even the ones that read nothing".
|
|
294
|
+
*
|
|
295
|
+
* ## The shape of the answer
|
|
296
|
+
*
|
|
297
|
+
* Each reason resolves to three things, and they are deliberately separate:
|
|
298
|
+
*
|
|
299
|
+
* - `challenge` — the RFC 6750 code for the `WWW-Authenticate` header. Only
|
|
300
|
+
* ever one of the two the spec defines for this situation, because a host's
|
|
301
|
+
* OAuth machinery keys off it;
|
|
302
|
+
* - `action` — what would actually fix it, for a client that automates;
|
|
303
|
+
* - `message` — one sentence an agent can relay to a person. English, like
|
|
304
|
+
* everything else a developer or a model reads here; a host that wants its
|
|
305
|
+
* own wording supplies it.
|
|
306
|
+
*
|
|
307
|
+
* Nothing here narrows `unverified`. Signature, issuer and audience stay
|
|
308
|
+
* collapsed into one answer on purpose — see `../oauth/access-token.ts` for why
|
|
309
|
+
* expiry is the single documented exception.
|
|
310
|
+
*/
|
|
311
|
+
/** Why a call was refused, across both the transport and the token verifier. */
|
|
312
|
+
type McpAuthFailureReason =
|
|
313
|
+
/** No `Authorization` header at all — the client has not connected yet. */
|
|
314
|
+
"no_token"
|
|
315
|
+
/** The token was fine until its `exp` passed. The common one, and recoverable. */
|
|
316
|
+
| "expired"
|
|
317
|
+
/** Signature, issuer or audience did not hold. Deliberately not narrowed. */
|
|
318
|
+
| "unverified"
|
|
319
|
+
/** Verified, but carrying no usable identity. */
|
|
320
|
+
| "incomplete"
|
|
321
|
+
/** The operator has not provisioned signing material, so nothing can verify. */
|
|
322
|
+
| "not_provisioned"
|
|
323
|
+
/** The whole MCP surface is switched off for this deployment. */
|
|
324
|
+
| "surface_disabled"
|
|
325
|
+
/** A valid token that lacks the scope this particular call needs. */
|
|
326
|
+
| "insufficient_scope";
|
|
327
|
+
/** What a client should do about it. */
|
|
328
|
+
type McpAuthRecovery =
|
|
329
|
+
/** Exchange the refresh token for a new access token, then retry. */
|
|
330
|
+
"refresh"
|
|
331
|
+
/** Re-run the authorization flow — a human has to approve it again. */
|
|
332
|
+
| "reconnect"
|
|
333
|
+
/** Nothing the client can do; the deployment has to change. */
|
|
334
|
+
| "contact_operator";
|
|
335
|
+
/** The resolved answer for one refusal. */
|
|
336
|
+
interface McpAuthFailure {
|
|
337
|
+
reason: McpAuthFailureReason;
|
|
338
|
+
/** The RFC 6750 code for the `WWW-Authenticate` challenge. */
|
|
339
|
+
challenge: "invalid_token" | "insufficient_scope";
|
|
340
|
+
action: McpAuthRecovery;
|
|
341
|
+
/** One sentence, written to be relayed to a person by an agent. */
|
|
342
|
+
message: string;
|
|
343
|
+
}
|
|
344
|
+
/** Resolve a reason to its challenge code, recovery and human-relayable message. */
|
|
345
|
+
declare function describeAuthFailure(reason: McpAuthFailureReason): McpAuthFailure;
|
|
346
|
+
/**
|
|
347
|
+
* The machine-readable half, carried in the JSON-RPC error's `data` member.
|
|
348
|
+
*
|
|
349
|
+
* A model reads `message`; a host that automates its connection lifecycle reads
|
|
350
|
+
* this. Both travel together so neither has to be inferred from the other.
|
|
351
|
+
*/
|
|
352
|
+
interface McpAuthFailureData {
|
|
353
|
+
reason: McpAuthFailureReason;
|
|
354
|
+
action: McpAuthRecovery;
|
|
355
|
+
/**
|
|
356
|
+
* Whether presenting a NEW token could succeed. `false` means the deployment
|
|
357
|
+
* itself is the problem, so a client that retries forever is wasting its time
|
|
358
|
+
* and the user's — and should say so rather than loop.
|
|
359
|
+
*/
|
|
360
|
+
recoverable: boolean;
|
|
361
|
+
}
|
|
362
|
+
/** Build the `data` payload for a refusal. */
|
|
363
|
+
declare function authFailureData(failure: McpAuthFailure): McpAuthFailureData;
|
|
364
|
+
|
|
278
365
|
/**
|
|
279
366
|
* The MCP JSON-RPC 2.0 request half of the Streamable HTTP transport.
|
|
280
367
|
*
|
|
@@ -312,9 +399,15 @@ interface JsonRpcResponse {
|
|
|
312
399
|
jsonrpc: "2.0";
|
|
313
400
|
id: string | number | null;
|
|
314
401
|
result?: unknown;
|
|
402
|
+
/**
|
|
403
|
+
* `data` is the JSON-RPC 2.0 optional member, and it is what carries the
|
|
404
|
+
* machine-readable half of a refusal ({@link McpAuthFailureData}) while
|
|
405
|
+
* `message` carries the half a model relays to a person.
|
|
406
|
+
*/
|
|
315
407
|
error?: {
|
|
316
408
|
code: number;
|
|
317
409
|
message: string;
|
|
410
|
+
data?: unknown;
|
|
318
411
|
};
|
|
319
412
|
}
|
|
320
413
|
/** What a client is told it connected to, in `initialize`'s `serverInfo`. */
|
|
@@ -355,8 +448,13 @@ interface McpJsonRpcOptions {
|
|
|
355
448
|
* `tools/call` then returns {@link UNAUTHORIZED_CODE}, which the host surfaces as
|
|
356
449
|
* HTTP 401. Discovery (`initialize`, `ping`, `tools/list`) stays open, so a client
|
|
357
450
|
* can read the surface before it has a token.
|
|
451
|
+
*
|
|
452
|
+
* `failure` is WHY `auth` is null, which only the host's verifier knows. It is
|
|
453
|
+
* optional so an existing caller keeps compiling, and passing it is what turns a
|
|
454
|
+
* refusal an agent can only report into one it can act on — see
|
|
455
|
+
* `./auth-failure.ts`.
|
|
358
456
|
*/
|
|
359
|
-
declare function handleMcpJsonRpc(request: JsonRpcRequest, registry: ToolRegistry, auth: RequestAuth | null, options: McpJsonRpcOptions): Promise<JsonRpcResponse | null>;
|
|
457
|
+
declare function handleMcpJsonRpc(request: JsonRpcRequest, registry: ToolRegistry, auth: RequestAuth | null, options: McpJsonRpcOptions, failure?: McpAuthFailureReason): Promise<JsonRpcResponse | null>;
|
|
360
458
|
|
|
361
459
|
/**
|
|
362
460
|
* The manifest is the committed source-of-truth artifact the CI drift gate
|
|
@@ -561,4 +659,4 @@ interface AuthorizationServerMetadata {
|
|
|
561
659
|
*/
|
|
562
660
|
declare function buildAuthorizationServerMetadata(input: AuthorizationServerMetadataInput): AuthorizationServerMetadata;
|
|
563
661
|
|
|
564
|
-
export { type AuthorizationServerMetadata, type AuthorizationServerMetadataInput, type AuthorizationServerPaths, type BuildManifestOptions, DispatchConfig, DispatchInputError, DispatchResult, GeneratedTool, HTTP_STATUS_META_KEY, type HttpMethod, type JsonRpcRequest, type JsonRpcResponse, JsonSchema, MCP_PROTOCOL_VERSION, type McpAnnotationDefaults, type McpEndpoint, type McpJsonRpcOptions, type McpServerInfo, type McpToolDescriptor, type McpToolResult, PROTECTED_RESOURCE_METADATA_PATH, type ProtectedResourceMetadata, type ProtectedResourceMetadataInput, type RegistryOptions, RequestAuth, type SurfaceLock, type SurfaceLockCheck, type ToolAnnotationOverrides, ToolAnnotations, ToolManifest, type ToolRegistry, UNAUTHORIZED_CODE, UnsupportedSchemaError, bearerChallenge, buildAuthorizationServerMetadata, buildManifest, buildProtectedResourceMetadata, createToolRegistry, dispatchTool, handleMcpJsonRpc, inlineSchemaRefs, redactResponseBody, redactResponseSchema, resolveToolAnnotations, serializeManifest, serializeSurfaceLock, surfaceDigest, surfaceLockProblem };
|
|
662
|
+
export { type AuthorizationServerMetadata, type AuthorizationServerMetadataInput, type AuthorizationServerPaths, type BuildManifestOptions, DispatchConfig, DispatchInputError, DispatchResult, GeneratedTool, HTTP_STATUS_META_KEY, type HttpMethod, type JsonRpcRequest, type JsonRpcResponse, JsonSchema, MCP_PROTOCOL_VERSION, type McpAnnotationDefaults, type McpAuthFailure, type McpAuthFailureData, type McpAuthFailureReason, type McpAuthRecovery, type McpEndpoint, type McpJsonRpcOptions, type McpServerInfo, type McpToolDescriptor, type McpToolResult, PROTECTED_RESOURCE_METADATA_PATH, type ProtectedResourceMetadata, type ProtectedResourceMetadataInput, type RegistryOptions, RequestAuth, type SurfaceLock, type SurfaceLockCheck, type ToolAnnotationOverrides, ToolAnnotations, ToolManifest, type ToolRegistry, UNAUTHORIZED_CODE, UnsupportedSchemaError, authFailureData, bearerChallenge, buildAuthorizationServerMetadata, buildManifest, buildProtectedResourceMetadata, createToolRegistry, describeAuthFailure, dispatchTool, handleMcpJsonRpc, inlineSchemaRefs, redactResponseBody, redactResponseSchema, resolveToolAnnotations, serializeManifest, serializeSurfaceLock, surfaceDigest, surfaceLockProblem };
|
package/dist/index.js
CHANGED
|
@@ -338,6 +338,57 @@ function createToolRegistry(options) {
|
|
|
338
338
|
}
|
|
339
339
|
__name(createToolRegistry, "createToolRegistry");
|
|
340
340
|
|
|
341
|
+
// src/server/auth-failure.ts
|
|
342
|
+
var FAILURES = {
|
|
343
|
+
no_token: {
|
|
344
|
+
challenge: "invalid_token",
|
|
345
|
+
action: "reconnect",
|
|
346
|
+
message: "This request carried no access token. Ask the user to connect this MCP server in their assistant's connector settings, then retry."
|
|
347
|
+
},
|
|
348
|
+
expired: {
|
|
349
|
+
challenge: "invalid_token",
|
|
350
|
+
action: "refresh",
|
|
351
|
+
message: "The access token has expired. A client that holds a refresh token should renew it and retry; if renewal also fails, ask the user to reconnect this MCP server in their assistant's connector settings."
|
|
352
|
+
},
|
|
353
|
+
unverified: {
|
|
354
|
+
challenge: "invalid_token",
|
|
355
|
+
action: "reconnect",
|
|
356
|
+
message: "The access token could not be verified for this server. Ask the user to reconnect this MCP server in their assistant's connector settings \u2014 a token issued for a different deployment will never verify here."
|
|
357
|
+
},
|
|
358
|
+
incomplete: {
|
|
359
|
+
challenge: "invalid_token",
|
|
360
|
+
action: "reconnect",
|
|
361
|
+
message: "The access token verified but carries no usable identity. Ask the user to reconnect this MCP server in their assistant's connector settings."
|
|
362
|
+
},
|
|
363
|
+
not_provisioned: {
|
|
364
|
+
challenge: "invalid_token",
|
|
365
|
+
action: "contact_operator",
|
|
366
|
+
message: "This server has no signing key provisioned, so no access token can be verified. Reconnecting will not help; the deployment's operator has to configure it."
|
|
367
|
+
},
|
|
368
|
+
surface_disabled: {
|
|
369
|
+
challenge: "invalid_token",
|
|
370
|
+
action: "contact_operator",
|
|
371
|
+
message: "The MCP surface is switched off on this deployment. Reconnecting will not help; the deployment's operator has to enable it."
|
|
372
|
+
},
|
|
373
|
+
insufficient_scope: {
|
|
374
|
+
challenge: "insufficient_scope",
|
|
375
|
+
action: "reconnect",
|
|
376
|
+
message: "The access token does not grant the scope this tool needs. Ask the user to reconnect this MCP server and approve the wider scope."
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
function describeAuthFailure(reason) {
|
|
380
|
+
return { reason, ...FAILURES[reason] };
|
|
381
|
+
}
|
|
382
|
+
__name(describeAuthFailure, "describeAuthFailure");
|
|
383
|
+
function authFailureData(failure) {
|
|
384
|
+
return {
|
|
385
|
+
reason: failure.reason,
|
|
386
|
+
action: failure.action,
|
|
387
|
+
recoverable: failure.action !== "contact_operator"
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
__name(authFailureData, "authFailureData");
|
|
391
|
+
|
|
341
392
|
// src/server/jsonrpc.ts
|
|
342
393
|
var MCP_PROTOCOL_VERSION = "2025-06-18";
|
|
343
394
|
var UNAUTHORIZED_CODE = -32001;
|
|
@@ -348,16 +399,28 @@ function ok(id, result) {
|
|
|
348
399
|
return { jsonrpc: "2.0", id: id ?? null, result };
|
|
349
400
|
}
|
|
350
401
|
__name(ok, "ok");
|
|
351
|
-
function fail(id, code, message) {
|
|
352
|
-
return {
|
|
402
|
+
function fail(id, code, message, data) {
|
|
403
|
+
return {
|
|
404
|
+
jsonrpc: "2.0",
|
|
405
|
+
id: id ?? null,
|
|
406
|
+
error: { code, message, ...data === void 0 ? {} : { data } }
|
|
407
|
+
};
|
|
353
408
|
}
|
|
354
409
|
__name(fail, "fail");
|
|
355
410
|
function isWellFormed(request) {
|
|
356
411
|
return request != null && typeof request === "object" && typeof request.method === "string";
|
|
357
412
|
}
|
|
358
413
|
__name(isWellFormed, "isWellFormed");
|
|
359
|
-
async function handleToolsCall(request, registry, auth) {
|
|
360
|
-
if (!auth)
|
|
414
|
+
async function handleToolsCall(request, registry, auth, failure) {
|
|
415
|
+
if (!auth) {
|
|
416
|
+
const described = describeAuthFailure(failure ?? "no_token");
|
|
417
|
+
return fail(
|
|
418
|
+
request.id,
|
|
419
|
+
UNAUTHORIZED_CODE,
|
|
420
|
+
described.message,
|
|
421
|
+
authFailureData(described)
|
|
422
|
+
);
|
|
423
|
+
}
|
|
361
424
|
const params = request.params ?? {};
|
|
362
425
|
if (!params.name) return fail(request.id, INVALID_PARAMS_CODE, "Missing tool name");
|
|
363
426
|
const result = await registry.callTool(params.name, params.arguments ?? {}, auth);
|
|
@@ -376,7 +439,7 @@ function handleInitialize(request, options) {
|
|
|
376
439
|
});
|
|
377
440
|
}
|
|
378
441
|
__name(handleInitialize, "handleInitialize");
|
|
379
|
-
async function handleMcpJsonRpc(request, registry, auth, options) {
|
|
442
|
+
async function handleMcpJsonRpc(request, registry, auth, options, failure) {
|
|
380
443
|
if (!isWellFormed(request)) {
|
|
381
444
|
return fail(request?.id ?? null, INVALID_REQUEST_CODE, "Invalid Request");
|
|
382
445
|
}
|
|
@@ -388,7 +451,7 @@ async function handleMcpJsonRpc(request, registry, auth, options) {
|
|
|
388
451
|
case "tools/list":
|
|
389
452
|
return ok(request.id, { tools: registry.listTools(auth ?? void 0) });
|
|
390
453
|
case "tools/call":
|
|
391
|
-
return handleToolsCall(request, registry, auth);
|
|
454
|
+
return handleToolsCall(request, registry, auth, failure);
|
|
392
455
|
default:
|
|
393
456
|
if (request.method.startsWith("notifications/")) return null;
|
|
394
457
|
return fail(request.id, METHOD_NOT_FOUND_CODE, `Method not found: ${request.method}`);
|
|
@@ -415,11 +478,13 @@ export {
|
|
|
415
478
|
UNAUTHORIZED_CODE,
|
|
416
479
|
UnsupportedSchemaError,
|
|
417
480
|
aiConnectPrompt,
|
|
481
|
+
authFailureData,
|
|
418
482
|
bearerChallenge,
|
|
419
483
|
buildAuthorizationServerMetadata,
|
|
420
484
|
buildManifest,
|
|
421
485
|
buildProtectedResourceMetadata,
|
|
422
486
|
createToolRegistry,
|
|
487
|
+
describeAuthFailure,
|
|
423
488
|
dispatchTool,
|
|
424
489
|
generateTools,
|
|
425
490
|
handleMcpJsonRpc,
|