@agent-native/skills 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/built-in-apps.d.ts +60 -0
- package/dist/built-in-apps.d.ts.map +1 -0
- package/dist/built-in-apps.js +105 -0
- package/dist/built-in-apps.js.map +1 -0
- package/dist/connect.d.ts +81 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +352 -0
- package/dist/connect.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +286 -61
- package/dist/index.js.map +1 -1
- package/dist/mcp-config-writers.d.ts +104 -0
- package/dist/mcp-config-writers.d.ts.map +1 -0
- package/dist/mcp-config-writers.js +418 -0
- package/dist/mcp-config-writers.js.map +1 -0
- package/dist/telemetry.d.ts +13 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +115 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -3,13 +3,21 @@
|
|
|
3
3
|
Install BuilderIO skill folders into Codex and Claude skill directories.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx @agent-native/skills add
|
|
7
|
-
npx @agent-native/skills add --skill quick-recap --client codex --scope project --update-instructions
|
|
8
|
-
npx @agent-native/skills add --skill visual-recap --client all --with-github-action
|
|
6
|
+
npx @agent-native/skills@latest add
|
|
7
|
+
npx @agent-native/skills@latest add --skill quick-recap --client codex --scope project --update-instructions
|
|
8
|
+
npx @agent-native/skills@latest add --skill visual-recap --client all --with-github-action
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Use `--skill <name>` one or more times to select specific skills, or omit it in
|
|
12
12
|
an interactive terminal to choose from a prompt. Use `--client codex`,
|
|
13
|
-
`--client claude-code`, or `--client all` to choose install targets
|
|
14
|
-
`--
|
|
15
|
-
and/or `CLAUDE.md` for
|
|
13
|
+
`--client claude-code`, or `--client all` to choose install targets; omitted
|
|
14
|
+
`--client` defaults to all supported clients. Add `--update-instructions` to
|
|
15
|
+
append an idempotent managed block to `AGENTS.md` and/or `CLAUDE.md` for
|
|
16
|
+
instruction-style skills.
|
|
17
|
+
|
|
18
|
+
Skill content comes from `BuilderIO/skills@main` at install/list time. That
|
|
19
|
+
means adding or updating public skills such as `quick-recap` or
|
|
20
|
+
`efficient-fable` in the skills repo is picked up by this CLI without publishing
|
|
21
|
+
a new `@agent-native/skills` package. `visual-plan` and `visual-recap` are also
|
|
22
|
+
installed from `BuilderIO/skills`; Agent Native syncs those two into the public
|
|
23
|
+
repo from the framework source of truth.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical "built-in agent-native app -> MCP server" descriptor registry.
|
|
3
|
+
*
|
|
4
|
+
* This is a dependency-free mirror of the manifest data encoded in
|
|
5
|
+
* `@agent-native/core`'s `BUILT_IN_APP_SKILLS` object
|
|
6
|
+
* (`packages/core/src/cli/skills.ts`, around line 1711+). The standalone
|
|
7
|
+
* `@agent-native/skills` installer uses this to know which skill name maps to
|
|
8
|
+
* which hosted MCP server without importing the (much heavier) core CLI module.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: every URL / serverName / alias / authMode below MUST stay byte-for-byte
|
|
11
|
+
* identical to core's `BUILT_IN_APP_SKILLS`. The provenance of each value is noted
|
|
12
|
+
* inline as `packages/core/src/cli/skills.ts:<line>`. If core changes, update here too
|
|
13
|
+
* (a future sync guard test should compare the two).
|
|
14
|
+
*/
|
|
15
|
+
/** Auth modes mirrored from core's `auth.mode` on each app-skill manifest. */
|
|
16
|
+
export type BuiltInAppAuthMode = "oauth" | "device" | "none";
|
|
17
|
+
/**
|
|
18
|
+
* Descriptor for a single built-in agent-native app and the hosted MCP server
|
|
19
|
+
* its skills connect to.
|
|
20
|
+
*/
|
|
21
|
+
export interface BuiltInAppMcp {
|
|
22
|
+
/** Stable app id (matches the manifest `id` in core). */
|
|
23
|
+
appId: string;
|
|
24
|
+
/** Human-facing display name. */
|
|
25
|
+
displayName: string;
|
|
26
|
+
/** Skill names this app exports (a skill name resolves back to this app). */
|
|
27
|
+
skillNames: string[];
|
|
28
|
+
/** MCP server name used when registering the connector. */
|
|
29
|
+
serverName: string;
|
|
30
|
+
/** Hosted MCP endpoint URL (`hosted.mcpUrl`). */
|
|
31
|
+
mcpUrl: string;
|
|
32
|
+
/** Hosted app base URL (`hosted.url`). */
|
|
33
|
+
hostedUrl: string;
|
|
34
|
+
/** Alternate server names accepted for this app (`mcp.aliases`). */
|
|
35
|
+
aliases?: string[];
|
|
36
|
+
/** Auth mode for connecting the MCP server (`auth.mode`). */
|
|
37
|
+
authMode: BuiltInAppAuthMode;
|
|
38
|
+
/** True when the app ships only as a local command (no remote refresh). */
|
|
39
|
+
localOnly?: boolean;
|
|
40
|
+
/** True when the app provides a PR Visual Recap GitHub Action workflow. */
|
|
41
|
+
hasGithubAction?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The built-in app -> MCP registry.
|
|
45
|
+
*
|
|
46
|
+
* Each entry's values are copied verbatim from core. Provenance per field is
|
|
47
|
+
* cited inline using the `packages/core/src/cli/skills.ts` line numbers.
|
|
48
|
+
*/
|
|
49
|
+
export declare const BUILT_IN_APP_MCP: BuiltInAppMcp[];
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the built-in app whose `skillNames` contains `skillName`
|
|
52
|
+
* (case-insensitive). Returns `undefined` when no built-in app exports it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveAppForSkill(skillName: string): BuiltInAppMcp | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* True when the given skill name maps to a built-in app with a hosted MCP
|
|
57
|
+
* server.
|
|
58
|
+
*/
|
|
59
|
+
export declare function appHasMcp(skillName: string): boolean;
|
|
60
|
+
//# sourceMappingURL=built-in-apps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"built-in-apps.d.ts","sourceRoot":"","sources":["../src/built-in-apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,2DAA2D;IAC3D,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6DAA6D;IAC7D,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,2EAA2E;IAC3E,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAAa,EAkE3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAChB,aAAa,GAAG,SAAS,CAM3B;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical "built-in agent-native app -> MCP server" descriptor registry.
|
|
3
|
+
*
|
|
4
|
+
* This is a dependency-free mirror of the manifest data encoded in
|
|
5
|
+
* `@agent-native/core`'s `BUILT_IN_APP_SKILLS` object
|
|
6
|
+
* (`packages/core/src/cli/skills.ts`, around line 1711+). The standalone
|
|
7
|
+
* `@agent-native/skills` installer uses this to know which skill name maps to
|
|
8
|
+
* which hosted MCP server without importing the (much heavier) core CLI module.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: every URL / serverName / alias / authMode below MUST stay byte-for-byte
|
|
11
|
+
* identical to core's `BUILT_IN_APP_SKILLS`. The provenance of each value is noted
|
|
12
|
+
* inline as `packages/core/src/cli/skills.ts:<line>`. If core changes, update here too
|
|
13
|
+
* (a future sync guard test should compare the two).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* The built-in app -> MCP registry.
|
|
17
|
+
*
|
|
18
|
+
* Each entry's values are copied verbatim from core. Provenance per field is
|
|
19
|
+
* cited inline using the `packages/core/src/cli/skills.ts` line numbers.
|
|
20
|
+
*/
|
|
21
|
+
export const BUILT_IN_APP_MCP = [
|
|
22
|
+
{
|
|
23
|
+
// core skills.ts:1822 (id), :1823 (displayName)
|
|
24
|
+
appId: "visual-plans",
|
|
25
|
+
displayName: "Agent-Native Plan",
|
|
26
|
+
// core skills.ts:1803 (skillName "visual-plan") + :1805 (extraSkills "visual-recap")
|
|
27
|
+
skillNames: ["visual-plan", "visual-recap"],
|
|
28
|
+
// core skills.ts:1830 (mcp.serverName + mcp.aliases)
|
|
29
|
+
serverName: "plan",
|
|
30
|
+
aliases: ["agent-native-plans"],
|
|
31
|
+
// core skills.ts:1828 (hosted.mcpUrl)
|
|
32
|
+
mcpUrl: "https://plan.agent-native.com/_agent-native/mcp",
|
|
33
|
+
// core skills.ts:1827 (hosted.url)
|
|
34
|
+
hostedUrl: "https://plan.agent-native.com",
|
|
35
|
+
// core skills.ts:1832 (auth.mode)
|
|
36
|
+
authMode: "oauth",
|
|
37
|
+
// core skills.ts:3321-3322 (githubActionPath gated on knownTarget === "visual-plans")
|
|
38
|
+
hasGithubAction: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
// core skills.ts:1716 (id), :1717 (displayName)
|
|
42
|
+
appId: "assets",
|
|
43
|
+
displayName: "Assets",
|
|
44
|
+
// core skills.ts:1713 (skillName)
|
|
45
|
+
skillNames: ["assets"],
|
|
46
|
+
// core skills.ts:1724 (mcp.serverName)
|
|
47
|
+
serverName: "agent-native-assets",
|
|
48
|
+
// core skills.ts:1722 (hosted.mcpUrl)
|
|
49
|
+
mcpUrl: "https://assets.agent-native.com/_agent-native/mcp",
|
|
50
|
+
// core skills.ts:1721 (hosted.url)
|
|
51
|
+
hostedUrl: "https://assets.agent-native.com",
|
|
52
|
+
// core skills.ts:1726 (auth.mode)
|
|
53
|
+
authMode: "oauth",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
// core skills.ts:1762 (id), :1763 (displayName)
|
|
57
|
+
appId: "design",
|
|
58
|
+
displayName: "Design",
|
|
59
|
+
// core skills.ts:1759 (skillName)
|
|
60
|
+
skillNames: ["design-exploration"],
|
|
61
|
+
// core skills.ts:1770 (mcp.serverName)
|
|
62
|
+
serverName: "agent-native-design",
|
|
63
|
+
// core skills.ts:1768 (hosted.mcpUrl)
|
|
64
|
+
mcpUrl: "https://design.agent-native.com/_agent-native/mcp",
|
|
65
|
+
// core skills.ts:1767 (hosted.url)
|
|
66
|
+
hostedUrl: "https://design.agent-native.com",
|
|
67
|
+
// core skills.ts:1772 (auth.mode)
|
|
68
|
+
authMode: "oauth",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
// core skills.ts:1881 (id), :1882 (displayName)
|
|
72
|
+
appId: "context-xray",
|
|
73
|
+
displayName: "Context X-Ray",
|
|
74
|
+
// core skills.ts:1877 (skillName)
|
|
75
|
+
skillNames: ["context-xray"],
|
|
76
|
+
// core skills.ts:1889 (mcp.serverName)
|
|
77
|
+
serverName: "agent-native-context-xray",
|
|
78
|
+
// core skills.ts:1887 (hosted.mcpUrl)
|
|
79
|
+
mcpUrl: "https://context-xray.agent-native.com/_agent-native/mcp",
|
|
80
|
+
// core skills.ts:1886 (hosted.url)
|
|
81
|
+
hostedUrl: "https://context-xray.agent-native.com",
|
|
82
|
+
// core skills.ts:1890 (auth.mode)
|
|
83
|
+
authMode: "none",
|
|
84
|
+
// core skills.ts:1878 (localOnly: true)
|
|
85
|
+
localOnly: true,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the built-in app whose `skillNames` contains `skillName`
|
|
90
|
+
* (case-insensitive). Returns `undefined` when no built-in app exports it.
|
|
91
|
+
*/
|
|
92
|
+
export function resolveAppForSkill(skillName) {
|
|
93
|
+
const needle = skillName.trim().toLowerCase();
|
|
94
|
+
if (!needle)
|
|
95
|
+
return undefined;
|
|
96
|
+
return BUILT_IN_APP_MCP.find((app) => app.skillNames.some((name) => name.toLowerCase() === needle));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* True when the given skill name maps to a built-in app with a hosted MCP
|
|
100
|
+
* server.
|
|
101
|
+
*/
|
|
102
|
+
export function appHasMcp(skillName) {
|
|
103
|
+
return resolveAppForSkill(skillName) !== undefined;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=built-in-apps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"built-in-apps.js","sourceRoot":"","sources":["../src/built-in-apps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAgCH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C;QACE,gDAAgD;QAChD,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,mBAAmB;QAChC,qFAAqF;QACrF,UAAU,EAAE,CAAC,aAAa,EAAE,cAAc,CAAC;QAC3C,qDAAqD;QACrD,UAAU,EAAE,MAAM;QAClB,OAAO,EAAE,CAAC,oBAAoB,CAAC;QAC/B,sCAAsC;QACtC,MAAM,EAAE,iDAAiD;QACzD,mCAAmC;QACnC,SAAS,EAAE,+BAA+B;QAC1C,kCAAkC;QAClC,QAAQ,EAAE,OAAO;QACjB,sFAAsF;QACtF,eAAe,EAAE,IAAI;KACtB;IACD;QACE,gDAAgD;QAChD,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,QAAQ;QACrB,kCAAkC;QAClC,UAAU,EAAE,CAAC,QAAQ,CAAC;QACtB,uCAAuC;QACvC,UAAU,EAAE,qBAAqB;QACjC,sCAAsC;QACtC,MAAM,EAAE,mDAAmD;QAC3D,mCAAmC;QACnC,SAAS,EAAE,iCAAiC;QAC5C,kCAAkC;QAClC,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,gDAAgD;QAChD,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,QAAQ;QACrB,kCAAkC;QAClC,UAAU,EAAE,CAAC,oBAAoB,CAAC;QAClC,uCAAuC;QACvC,UAAU,EAAE,qBAAqB;QACjC,sCAAsC;QACtC,MAAM,EAAE,mDAAmD;QAC3D,mCAAmC;QACnC,SAAS,EAAE,iCAAiC;QAC5C,kCAAkC;QAClC,QAAQ,EAAE,OAAO;KAClB;IACD;QACE,gDAAgD;QAChD,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE,eAAe;QAC5B,kCAAkC;QAClC,UAAU,EAAE,CAAC,cAAc,CAAC;QAC5B,uCAAuC;QACvC,UAAU,EAAE,2BAA2B;QACvC,sCAAsC;QACtC,MAAM,EAAE,yDAAyD;QACjE,mCAAmC;QACnC,SAAS,EAAE,uCAAuC;QAClD,kCAAkC;QAClC,QAAQ,EAAE,MAAM;QAChB,wCAAwC;QACxC,SAAS,EAAE,IAAI;KAChB;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,SAAiB;IAEjB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CACnC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,OAAO,kBAAkB,CAAC,SAAS,CAAC,KAAK,SAAS,CAAC;AACrD,CAAC","sourcesContent":["/**\n * Canonical \"built-in agent-native app -> MCP server\" descriptor registry.\n *\n * This is a dependency-free mirror of the manifest data encoded in\n * `@agent-native/core`'s `BUILT_IN_APP_SKILLS` object\n * (`packages/core/src/cli/skills.ts`, around line 1711+). The standalone\n * `@agent-native/skills` installer uses this to know which skill name maps to\n * which hosted MCP server without importing the (much heavier) core CLI module.\n *\n * IMPORTANT: every URL / serverName / alias / authMode below MUST stay byte-for-byte\n * identical to core's `BUILT_IN_APP_SKILLS`. The provenance of each value is noted\n * inline as `packages/core/src/cli/skills.ts:<line>`. If core changes, update here too\n * (a future sync guard test should compare the two).\n */\n\n/** Auth modes mirrored from core's `auth.mode` on each app-skill manifest. */\nexport type BuiltInAppAuthMode = \"oauth\" | \"device\" | \"none\";\n\n/**\n * Descriptor for a single built-in agent-native app and the hosted MCP server\n * its skills connect to.\n */\nexport interface BuiltInAppMcp {\n /** Stable app id (matches the manifest `id` in core). */\n appId: string;\n /** Human-facing display name. */\n displayName: string;\n /** Skill names this app exports (a skill name resolves back to this app). */\n skillNames: string[];\n /** MCP server name used when registering the connector. */\n serverName: string;\n /** Hosted MCP endpoint URL (`hosted.mcpUrl`). */\n mcpUrl: string;\n /** Hosted app base URL (`hosted.url`). */\n hostedUrl: string;\n /** Alternate server names accepted for this app (`mcp.aliases`). */\n aliases?: string[];\n /** Auth mode for connecting the MCP server (`auth.mode`). */\n authMode: BuiltInAppAuthMode;\n /** True when the app ships only as a local command (no remote refresh). */\n localOnly?: boolean;\n /** True when the app provides a PR Visual Recap GitHub Action workflow. */\n hasGithubAction?: boolean;\n}\n\n/**\n * The built-in app -> MCP registry.\n *\n * Each entry's values are copied verbatim from core. Provenance per field is\n * cited inline using the `packages/core/src/cli/skills.ts` line numbers.\n */\nexport const BUILT_IN_APP_MCP: BuiltInAppMcp[] = [\n {\n // core skills.ts:1822 (id), :1823 (displayName)\n appId: \"visual-plans\",\n displayName: \"Agent-Native Plan\",\n // core skills.ts:1803 (skillName \"visual-plan\") + :1805 (extraSkills \"visual-recap\")\n skillNames: [\"visual-plan\", \"visual-recap\"],\n // core skills.ts:1830 (mcp.serverName + mcp.aliases)\n serverName: \"plan\",\n aliases: [\"agent-native-plans\"],\n // core skills.ts:1828 (hosted.mcpUrl)\n mcpUrl: \"https://plan.agent-native.com/_agent-native/mcp\",\n // core skills.ts:1827 (hosted.url)\n hostedUrl: \"https://plan.agent-native.com\",\n // core skills.ts:1832 (auth.mode)\n authMode: \"oauth\",\n // core skills.ts:3321-3322 (githubActionPath gated on knownTarget === \"visual-plans\")\n hasGithubAction: true,\n },\n {\n // core skills.ts:1716 (id), :1717 (displayName)\n appId: \"assets\",\n displayName: \"Assets\",\n // core skills.ts:1713 (skillName)\n skillNames: [\"assets\"],\n // core skills.ts:1724 (mcp.serverName)\n serverName: \"agent-native-assets\",\n // core skills.ts:1722 (hosted.mcpUrl)\n mcpUrl: \"https://assets.agent-native.com/_agent-native/mcp\",\n // core skills.ts:1721 (hosted.url)\n hostedUrl: \"https://assets.agent-native.com\",\n // core skills.ts:1726 (auth.mode)\n authMode: \"oauth\",\n },\n {\n // core skills.ts:1762 (id), :1763 (displayName)\n appId: \"design\",\n displayName: \"Design\",\n // core skills.ts:1759 (skillName)\n skillNames: [\"design-exploration\"],\n // core skills.ts:1770 (mcp.serverName)\n serverName: \"agent-native-design\",\n // core skills.ts:1768 (hosted.mcpUrl)\n mcpUrl: \"https://design.agent-native.com/_agent-native/mcp\",\n // core skills.ts:1767 (hosted.url)\n hostedUrl: \"https://design.agent-native.com\",\n // core skills.ts:1772 (auth.mode)\n authMode: \"oauth\",\n },\n {\n // core skills.ts:1881 (id), :1882 (displayName)\n appId: \"context-xray\",\n displayName: \"Context X-Ray\",\n // core skills.ts:1877 (skillName)\n skillNames: [\"context-xray\"],\n // core skills.ts:1889 (mcp.serverName)\n serverName: \"agent-native-context-xray\",\n // core skills.ts:1887 (hosted.mcpUrl)\n mcpUrl: \"https://context-xray.agent-native.com/_agent-native/mcp\",\n // core skills.ts:1886 (hosted.url)\n hostedUrl: \"https://context-xray.agent-native.com\",\n // core skills.ts:1890 (auth.mode)\n authMode: \"none\",\n // core skills.ts:1878 (localOnly: true)\n localOnly: true,\n },\n];\n\n/**\n * Resolve the built-in app whose `skillNames` contains `skillName`\n * (case-insensitive). Returns `undefined` when no built-in app exports it.\n */\nexport function resolveAppForSkill(\n skillName: string,\n): BuiltInAppMcp | undefined {\n const needle = skillName.trim().toLowerCase();\n if (!needle) return undefined;\n return BUILT_IN_APP_MCP.find((app) =>\n app.skillNames.some((name) => name.toLowerCase() === needle),\n );\n}\n\n/**\n * True when the given skill name maps to a built-in app with a hosted MCP\n * server.\n */\nexport function appHasMcp(skillName: string): boolean {\n return resolveAppForSkill(skillName) !== undefined;\n}\n"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-server registration + authentication for `@agent-native/skills`.
|
|
3
|
+
*
|
|
4
|
+
* This is a dependency-free port of the MCP-config-writing + device-code/OAuth
|
|
5
|
+
* flow that lives in `@agent-native/core`'s `cli/connect.ts`. The skills package
|
|
6
|
+
* ships standalone (no `@agent-native/core` dependency), so this module
|
|
7
|
+
* re-implements just the registration surface against the shared on-disk
|
|
8
|
+
* writers in `./mcp-config-writers.js`. It writes the SAME config and speaks the
|
|
9
|
+
* SAME device-code/OAuth protocol as core.
|
|
10
|
+
*
|
|
11
|
+
* Two client families, exactly as in core:
|
|
12
|
+
* - OAuth-capable (claude-code, claude-code-cli): get a URL-only HTTP MCP
|
|
13
|
+
* entry (no bearer headers). The user authenticates in-host via standard
|
|
14
|
+
* remote MCP OAuth: restart Claude Code, run /mcp, choose Authenticate.
|
|
15
|
+
* - Device-code (codex, cowork): run the browser device-code flow against the
|
|
16
|
+
* descriptor's hosted URL, then write the entry WITH the minted bearer token
|
|
17
|
+
* + headers. Non-interactive (or no TTY) skips the flow and writes a
|
|
18
|
+
* URL-only entry, surfacing the exact `agent-native connect <url> --token`
|
|
19
|
+
* fallback command.
|
|
20
|
+
*
|
|
21
|
+
* Server contract (identical paths + JSON field names to core):
|
|
22
|
+
* POST <hostedUrl>/_agent-native/mcp/connect/device/start (no auth)
|
|
23
|
+
* body { client?, app? }
|
|
24
|
+
* → { device_code, user_code, verification_uri,
|
|
25
|
+
* verification_uri_complete, interval, expires_in }
|
|
26
|
+
* POST <hostedUrl>/_agent-native/mcp/connect/device/poll (no auth)
|
|
27
|
+
* body { device_code }
|
|
28
|
+
* → { status: "pending" }
|
|
29
|
+
* | { status: "approved", token, mcpUrl, serverName, mcpServerEntry }
|
|
30
|
+
* | { status: "expired" } | { status: "consumed" }
|
|
31
|
+
* | { status: "error" | "not_found", message? }
|
|
32
|
+
*
|
|
33
|
+
* Node-only. Node built-ins + global fetch only; no npm deps.
|
|
34
|
+
*/
|
|
35
|
+
import { ClientId } from "./mcp-config-writers.js";
|
|
36
|
+
/**
|
|
37
|
+
* Describes one MCP server to register. `serverName` is the canonical config
|
|
38
|
+
* key; `aliases` are additional config keys that point at the same MCP URL
|
|
39
|
+
* (e.g. `plan` + `agent-native-plans`). `hostedUrl` is the deployed app origin
|
|
40
|
+
* the device-code flow authenticates against; `mcpUrl` is the resolved MCP
|
|
41
|
+
* endpoint written into the config (defaults to `<hostedUrl>/_agent-native/mcp`
|
|
42
|
+
* when only `hostedUrl` is supplied).
|
|
43
|
+
*/
|
|
44
|
+
export interface McpDescriptor {
|
|
45
|
+
serverName: string;
|
|
46
|
+
mcpUrl: string;
|
|
47
|
+
aliases?: string[];
|
|
48
|
+
authMode?: "oauth" | "device" | "none";
|
|
49
|
+
hostedUrl?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface RegisterMcpOptions {
|
|
52
|
+
descriptor: McpDescriptor;
|
|
53
|
+
clients: ClientId[];
|
|
54
|
+
scope: "user" | "project";
|
|
55
|
+
baseDir: string;
|
|
56
|
+
interactive: boolean;
|
|
57
|
+
log?: (m: string) => void;
|
|
58
|
+
deps?: {
|
|
59
|
+
fetchImpl?: typeof fetch;
|
|
60
|
+
now?: () => number;
|
|
61
|
+
sleep?: (ms: number) => Promise<void>;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export interface RegisterMcpResult {
|
|
65
|
+
written: {
|
|
66
|
+
client: ClientId;
|
|
67
|
+
file: string;
|
|
68
|
+
}[];
|
|
69
|
+
authenticated: boolean;
|
|
70
|
+
guidance: string[];
|
|
71
|
+
}
|
|
72
|
+
export declare function supportsRemoteMcpOAuth(client: ClientId): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Register an MCP server (plus aliases) into the requested client configs,
|
|
75
|
+
* authenticating device-code clients via the browser flow when interactive.
|
|
76
|
+
*
|
|
77
|
+
* Idempotent: re-running replaces the same named entries. Never throws on a
|
|
78
|
+
* single client/key failing — failures are collected into `guidance`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function registerMcpServer(opts: RegisterMcpOptions): Promise<RegisterMcpResult>;
|
|
81
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,QAAQ,EAA2B,MAAM,yBAAyB,CAAC;AAmB5E;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;QACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACvC,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE;QAAE,MAAM,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC9C,aAAa,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA0CD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAEhE;AAsSD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,kBAAkB,GACvB,OAAO,CAAC,iBAAiB,CAAC,CAuI5B"}
|
package/dist/connect.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP-server registration + authentication for `@agent-native/skills`.
|
|
3
|
+
*
|
|
4
|
+
* This is a dependency-free port of the MCP-config-writing + device-code/OAuth
|
|
5
|
+
* flow that lives in `@agent-native/core`'s `cli/connect.ts`. The skills package
|
|
6
|
+
* ships standalone (no `@agent-native/core` dependency), so this module
|
|
7
|
+
* re-implements just the registration surface against the shared on-disk
|
|
8
|
+
* writers in `./mcp-config-writers.js`. It writes the SAME config and speaks the
|
|
9
|
+
* SAME device-code/OAuth protocol as core.
|
|
10
|
+
*
|
|
11
|
+
* Two client families, exactly as in core:
|
|
12
|
+
* - OAuth-capable (claude-code, claude-code-cli): get a URL-only HTTP MCP
|
|
13
|
+
* entry (no bearer headers). The user authenticates in-host via standard
|
|
14
|
+
* remote MCP OAuth: restart Claude Code, run /mcp, choose Authenticate.
|
|
15
|
+
* - Device-code (codex, cowork): run the browser device-code flow against the
|
|
16
|
+
* descriptor's hosted URL, then write the entry WITH the minted bearer token
|
|
17
|
+
* + headers. Non-interactive (or no TTY) skips the flow and writes a
|
|
18
|
+
* URL-only entry, surfacing the exact `agent-native connect <url> --token`
|
|
19
|
+
* fallback command.
|
|
20
|
+
*
|
|
21
|
+
* Server contract (identical paths + JSON field names to core):
|
|
22
|
+
* POST <hostedUrl>/_agent-native/mcp/connect/device/start (no auth)
|
|
23
|
+
* body { client?, app? }
|
|
24
|
+
* → { device_code, user_code, verification_uri,
|
|
25
|
+
* verification_uri_complete, interval, expires_in }
|
|
26
|
+
* POST <hostedUrl>/_agent-native/mcp/connect/device/poll (no auth)
|
|
27
|
+
* body { device_code }
|
|
28
|
+
* → { status: "pending" }
|
|
29
|
+
* | { status: "approved", token, mcpUrl, serverName, mcpServerEntry }
|
|
30
|
+
* | { status: "expired" } | { status: "consumed" }
|
|
31
|
+
* | { status: "error" | "not_found", message? }
|
|
32
|
+
*
|
|
33
|
+
* Node-only. Node built-ins + global fetch only; no npm deps.
|
|
34
|
+
*/
|
|
35
|
+
import { writeHttpEntryForClient } from "./mcp-config-writers.js";
|
|
36
|
+
const DEVICE_START_PATH = "/_agent-native/mcp/connect/device/start";
|
|
37
|
+
const DEVICE_POLL_PATH = "/_agent-native/mcp/connect/device/poll";
|
|
38
|
+
const MCP_PATH = "/_agent-native/mcp";
|
|
39
|
+
/** OAuth-capable clients (in-host remote MCP OAuth, never a local bearer). */
|
|
40
|
+
const REMOTE_MCP_OAUTH_CLIENTS = new Set([
|
|
41
|
+
"claude-code",
|
|
42
|
+
"claude-code-cli",
|
|
43
|
+
]);
|
|
44
|
+
/** Identical to core: ask the deployed app to expose the full action catalog. */
|
|
45
|
+
const MCP_FULL_CATALOG_HEADER = "X-Agent-Native-MCP-Full-Catalog";
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Helpers
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
export function supportsRemoteMcpOAuth(client) {
|
|
50
|
+
return REMOTE_MCP_OAUTH_CLIENTS.has(client);
|
|
51
|
+
}
|
|
52
|
+
function realSleep(ms) {
|
|
53
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
54
|
+
}
|
|
55
|
+
/** Trailing-slash-stripped origin+path for a hosted app URL. */
|
|
56
|
+
function stripTrailingSlash(url) {
|
|
57
|
+
return url.replace(/\/+$/, "");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the MCP endpoint URL for a descriptor. Prefers an explicit `mcpUrl`,
|
|
61
|
+
* otherwise derives `<hostedUrl>/_agent-native/mcp` (mirrors core's
|
|
62
|
+
* `mcpUrlForBaseUrl`). Returns `undefined` when neither is usable.
|
|
63
|
+
*/
|
|
64
|
+
function resolveMcpUrl(descriptor) {
|
|
65
|
+
if (descriptor.mcpUrl && descriptor.mcpUrl.trim()) {
|
|
66
|
+
return descriptor.mcpUrl.trim();
|
|
67
|
+
}
|
|
68
|
+
if (descriptor.hostedUrl && descriptor.hostedUrl.trim()) {
|
|
69
|
+
const base = stripTrailingSlash(descriptor.hostedUrl.trim());
|
|
70
|
+
return `${base}${MCP_PATH}`;
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
/** Base (origin) URL of the deployed app the device flow runs against. */
|
|
75
|
+
function resolveBaseUrl(descriptor) {
|
|
76
|
+
if (descriptor.hostedUrl && descriptor.hostedUrl.trim()) {
|
|
77
|
+
return stripTrailingSlash(descriptor.hostedUrl.trim());
|
|
78
|
+
}
|
|
79
|
+
// Fall back to stripping the MCP path off an explicit mcpUrl.
|
|
80
|
+
if (descriptor.mcpUrl && descriptor.mcpUrl.trim()) {
|
|
81
|
+
const trimmed = stripTrailingSlash(descriptor.mcpUrl.trim());
|
|
82
|
+
if (trimmed.endsWith(MCP_PATH)) {
|
|
83
|
+
return trimmed.slice(0, -MCP_PATH.length);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
/** All config keys to register: the canonical name plus any aliases (deduped). */
|
|
89
|
+
function configKeys(descriptor) {
|
|
90
|
+
const keys = [descriptor.serverName];
|
|
91
|
+
for (const alias of descriptor.aliases ?? []) {
|
|
92
|
+
if (alias && alias !== descriptor.serverName && !keys.includes(alias)) {
|
|
93
|
+
keys.push(alias);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return keys;
|
|
97
|
+
}
|
|
98
|
+
/** Always tag bearer-bearing entries so the client sees the full catalog. */
|
|
99
|
+
function withFullCatalogHeader(headers) {
|
|
100
|
+
return { ...(headers ?? {}), [MCP_FULL_CATALOG_HEADER]: "1" };
|
|
101
|
+
}
|
|
102
|
+
function responseMessage(json, fallback) {
|
|
103
|
+
const message = typeof json?.message === "string"
|
|
104
|
+
? json.message
|
|
105
|
+
: typeof json?.error === "string"
|
|
106
|
+
? json.error
|
|
107
|
+
: "";
|
|
108
|
+
return message.trim() || fallback;
|
|
109
|
+
}
|
|
110
|
+
async function postJson(fetchImpl, url, body) {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeout = setTimeout(() => controller.abort(), 30_000);
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetchImpl(url, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { "content-type": "application/json" },
|
|
117
|
+
body: JSON.stringify(body ?? {}),
|
|
118
|
+
signal: controller.signal,
|
|
119
|
+
});
|
|
120
|
+
let json = null;
|
|
121
|
+
try {
|
|
122
|
+
json = await response.json();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
json = null;
|
|
126
|
+
}
|
|
127
|
+
return { status: response.status, json };
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The exact no-browser fallback command core prints. Surfaced as guidance when
|
|
135
|
+
* a device-code client is asked to register non-interactively.
|
|
136
|
+
*/
|
|
137
|
+
function fallbackConnectCommand(baseUrl) {
|
|
138
|
+
return `npx @agent-native/core@latest connect ${baseUrl} --client all --token <token>`;
|
|
139
|
+
}
|
|
140
|
+
/** Write a URL-only entry (no bearer) for every config key, collecting files. */
|
|
141
|
+
function writeUrlOnlyEntries(clients, keys, mcpUrl, scope, baseDir, written, errors) {
|
|
142
|
+
for (const client of clients) {
|
|
143
|
+
for (const key of keys) {
|
|
144
|
+
try {
|
|
145
|
+
const file = writeHttpEntryForClient(client, key, mcpUrl, undefined, baseDir, scope, undefined);
|
|
146
|
+
written.push({ client, file });
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
errors.push(`Could not write ${key} for ${client}: ${err?.message ?? err}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Write a token+headers entry for every config key, collecting files. */
|
|
155
|
+
function writeAuthedEntries(clients, keys, mcpUrl, token, headers, scope, baseDir, written, errors) {
|
|
156
|
+
for (const client of clients) {
|
|
157
|
+
for (const key of keys) {
|
|
158
|
+
try {
|
|
159
|
+
const file = writeHttpEntryForClient(client, key, mcpUrl, token, baseDir, scope, withFullCatalogHeader(headers));
|
|
160
|
+
written.push({ client, file });
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
errors.push(`Could not write ${key} for ${client}: ${err?.message ?? err}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Device-code flow (dependency-free port of core's runDeviceFlow)
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
/**
|
|
172
|
+
* Run the device-code flow against `baseUrl` and return the approved grant, or
|
|
173
|
+
* `null` (after logging a clear message) on expiry/consumed/error/timeout. Same
|
|
174
|
+
* state machine and field handling as core; the spinner/browser-open are
|
|
175
|
+
* dropped since this runs inside a non-interactive installer context.
|
|
176
|
+
*/
|
|
177
|
+
async function runDeviceFlow(baseUrl, appSlug, clientArg, log, deps = {}) {
|
|
178
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
179
|
+
const sleep = deps.sleep ?? realSleep;
|
|
180
|
+
const now = deps.now ?? (() => Date.now());
|
|
181
|
+
let start;
|
|
182
|
+
try {
|
|
183
|
+
const { status, json } = await postJson(fetchImpl, `${baseUrl}${DEVICE_START_PATH}`, { client: clientArg, app: appSlug });
|
|
184
|
+
if (status < 200 || status >= 300 || !json?.device_code) {
|
|
185
|
+
log(` Could not start the connect flow on ${baseUrl} (HTTP ${status}). ` +
|
|
186
|
+
`Is this an agent-native app, and is it deployed with the connect ` +
|
|
187
|
+
`endpoint enabled?`);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
start = json;
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
log(` Could not reach ${baseUrl} (${err?.message ?? err}). ` +
|
|
194
|
+
`Check the URL and your network.`);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
const interval = Math.max(1, Number(start.interval) || 5);
|
|
198
|
+
const expiresIn = Math.max(interval, Number(start.expires_in) || 600);
|
|
199
|
+
const deadline = now() + expiresIn * 1000;
|
|
200
|
+
log("");
|
|
201
|
+
log(` Connecting to ${baseUrl}`);
|
|
202
|
+
log("");
|
|
203
|
+
log(` Your code: ${start.user_code}`);
|
|
204
|
+
log(` Open: ${start.verification_uri_complete}`);
|
|
205
|
+
log("");
|
|
206
|
+
log(" Approve in the browser to finish.");
|
|
207
|
+
while (now() < deadline) {
|
|
208
|
+
let poll;
|
|
209
|
+
try {
|
|
210
|
+
const { status, json } = await postJson(fetchImpl, `${baseUrl}${DEVICE_POLL_PATH}`, { device_code: start.device_code });
|
|
211
|
+
if (status < 200 || status >= 300) {
|
|
212
|
+
log(` Connect polling failed (HTTP ${status}): ` +
|
|
213
|
+
responseMessage(json, "server returned an error."));
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
poll = (json ?? { status: "pending" });
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Transient network error — keep polling until the deadline.
|
|
220
|
+
poll = { status: "pending" };
|
|
221
|
+
}
|
|
222
|
+
if (poll.status === "approved") {
|
|
223
|
+
const token = poll.token ?? "";
|
|
224
|
+
const mcpUrl = poll.mcpUrl ?? `${baseUrl}${MCP_PATH}`;
|
|
225
|
+
const serverName = poll.serverName ?? appSlug;
|
|
226
|
+
const headers = poll.mcpServerEntry &&
|
|
227
|
+
typeof poll.mcpServerEntry === "object" &&
|
|
228
|
+
poll.mcpServerEntry.headers &&
|
|
229
|
+
typeof poll.mcpServerEntry.headers === "object"
|
|
230
|
+
? poll.mcpServerEntry.headers
|
|
231
|
+
: undefined;
|
|
232
|
+
log(" Approved.");
|
|
233
|
+
return { token: token || undefined, mcpUrl, serverName, headers };
|
|
234
|
+
}
|
|
235
|
+
if (poll.status === "expired") {
|
|
236
|
+
log(" The connect request expired before it was approved.");
|
|
237
|
+
log(" Run the command again to retry.");
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
if (poll.status === "consumed") {
|
|
241
|
+
log(" This connect code was already used. Run the command again.");
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
if (poll.status === "error" || poll.status === "not_found") {
|
|
245
|
+
log(` Connect polling failed: ${responseMessage(poll, poll.status === "not_found"
|
|
246
|
+
? "device code was not found."
|
|
247
|
+
: "server returned an error.")}`);
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
await sleep(interval * 1000);
|
|
251
|
+
}
|
|
252
|
+
log(" Timed out waiting for approval. Run the command again to retry.");
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Public entry point
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
/**
|
|
259
|
+
* Register an MCP server (plus aliases) into the requested client configs,
|
|
260
|
+
* authenticating device-code clients via the browser flow when interactive.
|
|
261
|
+
*
|
|
262
|
+
* Idempotent: re-running replaces the same named entries. Never throws on a
|
|
263
|
+
* single client/key failing — failures are collected into `guidance`.
|
|
264
|
+
*/
|
|
265
|
+
export async function registerMcpServer(opts) {
|
|
266
|
+
const { descriptor, scope, baseDir, interactive } = opts;
|
|
267
|
+
const log = opts.log ?? (() => { });
|
|
268
|
+
const deps = opts.deps ?? {};
|
|
269
|
+
const written = [];
|
|
270
|
+
const guidance = [];
|
|
271
|
+
const errors = [];
|
|
272
|
+
let authenticated = false;
|
|
273
|
+
const keys = configKeys(descriptor);
|
|
274
|
+
const mcpUrl = resolveMcpUrl(descriptor);
|
|
275
|
+
if (!mcpUrl) {
|
|
276
|
+
return {
|
|
277
|
+
written,
|
|
278
|
+
authenticated,
|
|
279
|
+
guidance: [
|
|
280
|
+
`Cannot register "${descriptor.serverName}": no mcpUrl or hostedUrl supplied.`,
|
|
281
|
+
],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const authMode = descriptor.authMode ?? "device";
|
|
285
|
+
// authMode "none" (e.g. context-xray): URL-only for ALL clients, no auth.
|
|
286
|
+
if (authMode === "none") {
|
|
287
|
+
writeUrlOnlyEntries(opts.clients, keys, mcpUrl, scope, baseDir, written, errors);
|
|
288
|
+
return { written, authenticated, guidance: [...guidance, ...errors] };
|
|
289
|
+
}
|
|
290
|
+
// Split into OAuth-capable and device-code clients, exactly like core.
|
|
291
|
+
const oauthClients = opts.clients.filter((c) => supportsRemoteMcpOAuth(c));
|
|
292
|
+
const deviceClients = opts.clients.filter((c) => !supportsRemoteMcpOAuth(c));
|
|
293
|
+
// OAuth clients always get URL-only entries (in-host OAuth, no local bearer).
|
|
294
|
+
if (oauthClients.length > 0) {
|
|
295
|
+
writeUrlOnlyEntries(oauthClients, keys, mcpUrl, scope, baseDir, written, errors);
|
|
296
|
+
guidance.push(`${describeClients(oauthClients)}: wrote URL-only MCP config (no bearer headers).`, "Next: restart Claude Code, run /mcp, and choose Authenticate.");
|
|
297
|
+
}
|
|
298
|
+
// Device-code clients.
|
|
299
|
+
if (deviceClients.length > 0) {
|
|
300
|
+
const baseUrl = resolveBaseUrl(descriptor);
|
|
301
|
+
// We only reach here for authMode "oauth"/"device" (authMode "none" returned
|
|
302
|
+
// earlier). Run the flow only when interactive AND we have a hosted URL to
|
|
303
|
+
// authenticate against; otherwise fall back to URL-only.
|
|
304
|
+
const canRunFlow = interactive && !!baseUrl;
|
|
305
|
+
if (!canRunFlow) {
|
|
306
|
+
writeUrlOnlyEntries(deviceClients, keys, mcpUrl, scope, baseDir, written, errors);
|
|
307
|
+
if (baseUrl) {
|
|
308
|
+
guidance.push(`${describeClients(deviceClients)}: wrote URL-only MCP config (no token).`, `To authenticate non-interactively, run: ${fallbackConnectCommand(baseUrl)}`);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
guidance.push(`${describeClients(deviceClients)}: wrote URL-only MCP config (no hosted URL to authenticate against).`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
const appSlug = appSlugFor(descriptor, baseUrl);
|
|
316
|
+
const clientArg = deviceClients.length === 1 ? deviceClients[0] : "all";
|
|
317
|
+
const grant = await runDeviceFlow(baseUrl, appSlug, clientArg, log, deps);
|
|
318
|
+
if (grant && grant.token) {
|
|
319
|
+
// Write authed entries; honour the server's resolved mcpUrl when given.
|
|
320
|
+
const resolvedUrl = grant.mcpUrl || mcpUrl;
|
|
321
|
+
writeAuthedEntries(deviceClients, keys, resolvedUrl, grant.token, grant.headers, scope, baseDir, written, errors);
|
|
322
|
+
authenticated = true;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
// Flow failed (or approved with no token) — fall back to URL-only and
|
|
326
|
+
// surface the exact recovery command.
|
|
327
|
+
writeUrlOnlyEntries(deviceClients, keys, mcpUrl, scope, baseDir, written, errors);
|
|
328
|
+
guidance.push(`${describeClients(deviceClients)}: authentication did not complete; wrote URL-only MCP config.`, `To finish, run: ${fallbackConnectCommand(baseUrl)}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return { written, authenticated, guidance: [...guidance, ...errors] };
|
|
333
|
+
}
|
|
334
|
+
function describeClients(clients) {
|
|
335
|
+
return clients.join(", ");
|
|
336
|
+
}
|
|
337
|
+
/** Derive the `app` slug the device-start endpoint expects. */
|
|
338
|
+
function appSlugFor(descriptor, baseUrl) {
|
|
339
|
+
// Prefer the descriptor's server name without the agent-native prefix.
|
|
340
|
+
const name = descriptor.serverName.replace(/^agent-native-/, "");
|
|
341
|
+
if (name)
|
|
342
|
+
return name;
|
|
343
|
+
try {
|
|
344
|
+
const host = new URL(baseUrl).hostname;
|
|
345
|
+
const first = host.split(".")[0];
|
|
346
|
+
return first && first !== "www" ? first : "app";
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return "app";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=connect.js.map
|