crewly 1.20.246 → 1.20.248

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.
Files changed (72) hide show
  1. package/dist/backend/backend/src/constants.d.ts +31 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +31 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts +1 -0
  6. package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts.map +1 -1
  7. package/dist/backend/backend/src/controllers/connector/connector.controller.js +7 -0
  8. package/dist/backend/backend/src/controllers/connector/connector.controller.js.map +1 -1
  9. package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts +1 -0
  10. package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts.map +1 -1
  11. package/dist/backend/backend/src/controllers/connector/connector.routes.js +7 -0
  12. package/dist/backend/backend/src/controllers/connector/connector.routes.js.map +1 -1
  13. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts +55 -0
  14. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts.map +1 -0
  15. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js +148 -0
  16. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js.map +1 -0
  17. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  18. package/dist/backend/backend/src/services/agent/agent-registration.service.js +5 -0
  19. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  20. package/dist/backend/backend/src/services/connector/connector-access.service.d.ts +8 -0
  21. package/dist/backend/backend/src/services/connector/connector-access.service.d.ts.map +1 -1
  22. package/dist/backend/backend/src/services/connector/connector-access.service.js +17 -0
  23. package/dist/backend/backend/src/services/connector/connector-access.service.js.map +1 -1
  24. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts +77 -0
  25. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -0
  26. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js +132 -0
  27. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -0
  28. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts +75 -0
  29. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts.map +1 -0
  30. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js +155 -0
  31. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js.map +1 -0
  32. package/dist/backend/backend/src/services/connector/remote-mcp.service.d.ts +192 -0
  33. package/dist/backend/backend/src/services/connector/remote-mcp.service.d.ts.map +1 -0
  34. package/dist/backend/backend/src/services/connector/remote-mcp.service.js +334 -0
  35. package/dist/backend/backend/src/services/connector/remote-mcp.service.js.map +1 -0
  36. package/dist/backend/backend/src/services/core/credential-files.d.ts.map +1 -1
  37. package/dist/backend/backend/src/services/core/credential-files.js +4 -1
  38. package/dist/backend/backend/src/services/core/credential-files.js.map +1 -1
  39. package/dist/backend/backend/src/services/worktree/docker-reclaim.d.ts +54 -0
  40. package/dist/backend/backend/src/services/worktree/docker-reclaim.d.ts.map +1 -0
  41. package/dist/backend/backend/src/services/worktree/docker-reclaim.js +102 -0
  42. package/dist/backend/backend/src/services/worktree/docker-reclaim.js.map +1 -0
  43. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts +8 -0
  44. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.d.ts.map +1 -1
  45. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js +23 -0
  46. package/dist/backend/backend/src/services/worktree/worktree-janitor.service.js.map +1 -1
  47. package/dist/backend/build-info.json +2 -2
  48. package/dist/cli/backend/src/constants.d.ts +31 -0
  49. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  50. package/dist/cli/backend/src/constants.js +31 -0
  51. package/dist/cli/backend/src/constants.js.map +1 -1
  52. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  53. package/dist/cli/backend/src/services/agent/agent-registration.service.js +5 -0
  54. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  55. package/dist/cli/backend/src/services/connector/connector-access.service.d.ts +85 -0
  56. package/dist/cli/backend/src/services/connector/connector-access.service.d.ts.map +1 -0
  57. package/dist/cli/backend/src/services/connector/connector-access.service.js +130 -0
  58. package/dist/cli/backend/src/services/connector/connector-access.service.js.map +1 -0
  59. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts +77 -0
  60. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -0
  61. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js +132 -0
  62. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -0
  63. package/dist/cli/backend/src/services/connector/remote-mcp.service.d.ts +192 -0
  64. package/dist/cli/backend/src/services/connector/remote-mcp.service.d.ts.map +1 -0
  65. package/dist/cli/backend/src/services/connector/remote-mcp.service.js +334 -0
  66. package/dist/cli/backend/src/services/connector/remote-mcp.service.js.map +1 -0
  67. package/dist/cli/backend/src/services/core/credential-files.d.ts.map +1 -1
  68. package/dist/cli/backend/src/services/core/credential-files.js +4 -1
  69. package/dist/cli/backend/src/services/core/credential-files.js.map +1 -1
  70. package/frontend/dist/assets/{index-c9630c55.js → index-8f4d482e.js} +277 -277
  71. package/frontend/dist/index.html +1 -1
  72. package/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Which agents may use a connected account.
3
+ *
4
+ * A connector (Google Workspace, Canva, Microsoft To Do, …) is one grant for the whole
5
+ * instance, so without this every agent can read the owner's mail the
6
+ * moment the owner connects it. Skills carry `X-Agent-Session`, so the
7
+ * backend can tell which role is calling and refuse.
8
+ *
9
+ * The default is **open**: a connector with no allowlist is usable by every
10
+ * agent, which is what installs had before this existed. An allowlist names
11
+ * exactly the roles allowed — `orchestrator` included, so the owner can
12
+ * keep even the orc out of their inbox. The owner's own calls (the
13
+ * dashboard, no `X-Agent-Session`) are never gated.
14
+ *
15
+ * Stored at `<CREWLY_HOME>/connector-access.json`.
16
+ *
17
+ * @module services/connector/connector-access.service
18
+ */
19
+ /** Per-connector access rule. */
20
+ export interface ConnectorAccess {
21
+ /** Roles allowed to use it. Empty = every agent. */
22
+ allowedRoles: string[];
23
+ }
24
+ /** The whole file: connector id → rule. */
25
+ export type ConnectorAccessMap = Record<string, ConnectorAccess>;
26
+ /** Connector ids the backend gates (must match the frontend catalog). */
27
+ export declare const GATED_CONNECTORS: readonly ["google-workspace", "canva", "microsoft-todo"];
28
+ /** One of {@link GATED_CONNECTORS}. */
29
+ export type GatedConnectorId = (typeof GATED_CONNECTORS)[number];
30
+ /**
31
+ * Reads and writes the per-connector role allowlists.
32
+ */
33
+ export declare class ConnectorAccessService {
34
+ private static instance;
35
+ private readonly logger;
36
+ private readonly filePath;
37
+ private cache;
38
+ /**
39
+ * @param crewlyHome - Override the home directory (tests)
40
+ */
41
+ constructor(crewlyHome?: string);
42
+ static getInstance(): ConnectorAccessService;
43
+ /** Reset the singleton (tests). */
44
+ static resetInstance(): void;
45
+ /**
46
+ * Every rule on file.
47
+ *
48
+ * @returns Connector id → rule (connectors with no rule are absent)
49
+ */
50
+ list(): Promise<ConnectorAccessMap>;
51
+ /**
52
+ * The allowlist for one connector.
53
+ *
54
+ * @param connectorId - Connector id
55
+ * @returns Its roles (empty = open to every agent)
56
+ */
57
+ allowedRoles(connectorId: string): Promise<string[]>;
58
+ /**
59
+ * Replace a connector's allowlist.
60
+ *
61
+ * @param connectorId - Connector id
62
+ * @param roles - Allowed roles; empty opens it to every agent
63
+ * @returns The stored rule
64
+ */
65
+ setAllowedRoles(connectorId: string, roles: string[]): Promise<ConnectorAccess>;
66
+ /**
67
+ * Drop a connector's rule (it goes back to open). Used when the connector
68
+ * itself is removed, so a later one with the same id starts open.
69
+ *
70
+ * @param connectorId - Connector id
71
+ * @returns True when a rule existed
72
+ */
73
+ removeRule(connectorId: string): Promise<boolean>;
74
+ /**
75
+ * Whether a caller may use a connector.
76
+ *
77
+ * @param connectorId - Connector id
78
+ * @param role - Caller role; undefined = the owner (always allowed)
79
+ * @returns True when allowed
80
+ */
81
+ isAllowed(connectorId: string, role: string | undefined): Promise<boolean>;
82
+ /** Drop the in-memory copy (tests, or an external edit of the file). */
83
+ invalidate(): void;
84
+ }
85
+ //# sourceMappingURL=connector-access.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-access.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/connector-access.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAEjE,yEAAyE;AACzE,eAAO,MAAM,gBAAgB,0DAA2D,CAAC;AAEzF,uCAAuC;AACvC,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAKjE;;GAEG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAuC;IAC9D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,KAAK,CAAmC;IAEhD;;OAEG;gBACS,UAAU,CAAC,EAAE,MAAM;IAK/B,MAAM,CAAC,WAAW,IAAI,sBAAsB;IAK5C,mCAAmC;IACnC,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAYzC;;;;;OAKG;IACG,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAI1D;;;;;;OAMG;IACG,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC;IAUrF;;;;;;OAMG;IACG,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUvD;;;;;;OAMG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhF,wEAAwE;IACxE,UAAU,IAAI,IAAI;CAGnB"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Which agents may use a connected account.
3
+ *
4
+ * A connector (Google Workspace, Canva, Microsoft To Do, …) is one grant for the whole
5
+ * instance, so without this every agent can read the owner's mail the
6
+ * moment the owner connects it. Skills carry `X-Agent-Session`, so the
7
+ * backend can tell which role is calling and refuse.
8
+ *
9
+ * The default is **open**: a connector with no allowlist is usable by every
10
+ * agent, which is what installs had before this existed. An allowlist names
11
+ * exactly the roles allowed — `orchestrator` included, so the owner can
12
+ * keep even the orc out of their inbox. The owner's own calls (the
13
+ * dashboard, no `X-Agent-Session`) are never gated.
14
+ *
15
+ * Stored at `<CREWLY_HOME>/connector-access.json`.
16
+ *
17
+ * @module services/connector/connector-access.service
18
+ */
19
+ import * as path from 'path';
20
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
21
+ import { LoggerService } from '../core/logger.service.js';
22
+ import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
23
+ /** Connector ids the backend gates (must match the frontend catalog). */
24
+ export const GATED_CONNECTORS = ['google-workspace', 'canva', 'microsoft-todo'];
25
+ /** Filename under CREWLY_HOME. */
26
+ const FILE_NAME = 'connector-access.json';
27
+ /**
28
+ * Reads and writes the per-connector role allowlists.
29
+ */
30
+ export class ConnectorAccessService {
31
+ static instance = null;
32
+ logger;
33
+ filePath;
34
+ cache = null;
35
+ /**
36
+ * @param crewlyHome - Override the home directory (tests)
37
+ */
38
+ constructor(crewlyHome) {
39
+ this.logger = LoggerService.getInstance().createComponentLogger('ConnectorAccess');
40
+ this.filePath = path.join(crewlyHome || getCrewlyHomePath(), FILE_NAME);
41
+ }
42
+ static getInstance() {
43
+ if (!ConnectorAccessService.instance)
44
+ ConnectorAccessService.instance = new ConnectorAccessService();
45
+ return ConnectorAccessService.instance;
46
+ }
47
+ /** Reset the singleton (tests). */
48
+ static resetInstance() {
49
+ ConnectorAccessService.instance = null;
50
+ }
51
+ /**
52
+ * Every rule on file.
53
+ *
54
+ * @returns Connector id → rule (connectors with no rule are absent)
55
+ */
56
+ async list() {
57
+ if (this.cache)
58
+ return this.cache;
59
+ const raw = await safeReadJson(this.filePath, {}, this.logger);
60
+ const clean = {};
61
+ for (const [id, rule] of Object.entries(raw ?? {})) {
62
+ const roles = Array.isArray(rule?.allowedRoles) ? rule.allowedRoles.map((r) => String(r).toLowerCase()).filter(Boolean) : [];
63
+ clean[id] = { allowedRoles: [...new Set(roles)] };
64
+ }
65
+ this.cache = clean;
66
+ return clean;
67
+ }
68
+ /**
69
+ * The allowlist for one connector.
70
+ *
71
+ * @param connectorId - Connector id
72
+ * @returns Its roles (empty = open to every agent)
73
+ */
74
+ async allowedRoles(connectorId) {
75
+ return (await this.list())[connectorId]?.allowedRoles ?? [];
76
+ }
77
+ /**
78
+ * Replace a connector's allowlist.
79
+ *
80
+ * @param connectorId - Connector id
81
+ * @param roles - Allowed roles; empty opens it to every agent
82
+ * @returns The stored rule
83
+ */
84
+ async setAllowedRoles(connectorId, roles) {
85
+ const normalized = [...new Set((roles ?? []).map((r) => String(r).trim().toLowerCase()).filter(Boolean))];
86
+ const map = { ...(await this.list()) };
87
+ map[connectorId] = { allowedRoles: normalized };
88
+ await atomicWriteJson(this.filePath, map);
89
+ this.cache = map;
90
+ this.logger.info('Connector access updated', { connectorId, allowedRoles: normalized });
91
+ return map[connectorId];
92
+ }
93
+ /**
94
+ * Drop a connector's rule (it goes back to open). Used when the connector
95
+ * itself is removed, so a later one with the same id starts open.
96
+ *
97
+ * @param connectorId - Connector id
98
+ * @returns True when a rule existed
99
+ */
100
+ async removeRule(connectorId) {
101
+ const map = { ...(await this.list()) };
102
+ if (!(connectorId in map))
103
+ return false;
104
+ delete map[connectorId];
105
+ await atomicWriteJson(this.filePath, map);
106
+ this.cache = map;
107
+ this.logger.info('Connector access rule removed', { connectorId });
108
+ return true;
109
+ }
110
+ /**
111
+ * Whether a caller may use a connector.
112
+ *
113
+ * @param connectorId - Connector id
114
+ * @param role - Caller role; undefined = the owner (always allowed)
115
+ * @returns True when allowed
116
+ */
117
+ async isAllowed(connectorId, role) {
118
+ if (!role)
119
+ return true; // the owner / dashboard
120
+ const roles = await this.allowedRoles(connectorId);
121
+ if (roles.length === 0)
122
+ return true; // no rule = open
123
+ return roles.includes(role.toLowerCase());
124
+ }
125
+ /** Drop the in-memory copy (tests, or an external edit of the file). */
126
+ invalidate() {
127
+ this.cache = null;
128
+ }
129
+ }
130
+ //# sourceMappingURL=connector-access.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connector-access.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/connector-access.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Hand remote MCP servers to an agent at launch.
3
+ *
4
+ * For each server whose `mcp:<id>` allowlist admits the agent's role:
5
+ *
6
+ * | Runtime | How |
7
+ * |---|---|
8
+ * | Claude Code | `--mcp-config '<CREWLY_HOME>/runtime/remote-mcp/<session>/claude-mcp.json'` (adds to the project `.mcp.json`; no `--strict-mcp-config`) |
9
+ * | Codex | `-c "mcp_servers.<id>.url=$(cat '<file>')"` (+ `http_headers`), so the secret never appears on the typed command line |
10
+ * | Gemini CLI, Antigravity, OpenCode, crewly-agent | skipped with a log line |
11
+ *
12
+ * Every file is written under CREWLY_HOME with mode 0600 (directory 0700),
13
+ * never into the project. The returned flags carry only file paths.
14
+ *
15
+ * @module services/connector/remote-mcp-launch.service
16
+ */
17
+ import { RemoteMcpService, type RemoteMcpServer } from './remote-mcp.service.js';
18
+ /** Input for {@link buildRemoteMcpLaunchFlags}. */
19
+ export interface RemoteMcpLaunchInput {
20
+ /** PTY session name. */
21
+ sessionName: string;
22
+ /** Agent role (decides the allowlists). */
23
+ role: string;
24
+ /** Runtime being launched. */
25
+ runtimeType: string;
26
+ /** Override CREWLY_HOME (tests). */
27
+ crewlyHome?: string;
28
+ /** Override the store (tests). */
29
+ service?: RemoteMcpService;
30
+ }
31
+ /** What {@link buildRemoteMcpLaunchFlags} decided. */
32
+ export interface RemoteMcpLaunchResult {
33
+ /** Flags to add to the launch command (already shell-quoted). */
34
+ flags: string[];
35
+ /** Server ids handed to the agent. */
36
+ servers: string[];
37
+ /** Why nothing was added, when nothing was. */
38
+ skipped?: 'no-servers' | 'unsupported-runtime' | 'error';
39
+ }
40
+ /**
41
+ * The per-session directory for launch files.
42
+ *
43
+ * @param crewlyHome - CREWLY_HOME
44
+ * @param sessionName - PTY session name
45
+ * @returns Absolute directory
46
+ */
47
+ export declare function remoteMcpSessionDir(crewlyHome: string, sessionName: string): string;
48
+ /**
49
+ * Claude Code's `--mcp-config` body.
50
+ *
51
+ * @param servers - Allowed servers
52
+ * @returns `{ mcpServers: { <id>: { type: 'http', url, headers? } } }`
53
+ */
54
+ export declare function buildClaudeMcpConfig(servers: RemoteMcpServer[]): {
55
+ mcpServers: Record<string, {
56
+ type: 'http';
57
+ url: string;
58
+ headers?: Record<string, string>;
59
+ }>;
60
+ };
61
+ /**
62
+ * A TOML inline table of headers (JSON strings are valid TOML basic strings).
63
+ *
64
+ * @param headers - Header map
65
+ * @returns e.g. `{ "Authorization" = "Bearer x" }`
66
+ */
67
+ export declare function toTomlInlineTable(headers: Record<string, string>): string;
68
+ /**
69
+ * Write the launch files for one agent and return the flags that load them.
70
+ * Never throws: on any failure the agent launches without remote MCP and
71
+ * the error is logged (without secrets).
72
+ *
73
+ * @param input - Session, role, runtime
74
+ * @returns Flags and the server ids handed over
75
+ */
76
+ export declare function buildRemoteMcpLaunchFlags(input: RemoteMcpLaunchInput): Promise<RemoteMcpLaunchResult>;
77
+ //# sourceMappingURL=remote-mcp-launch.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-mcp-launch.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/remote-mcp-launch.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,OAAO,EAAE,gBAAgB,EAAmB,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAIlG,mDAAmD;AACnD,MAAM,WAAW,oBAAoB;IACnC,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,OAAO,CAAC,EAAE,gBAAgB,CAAC;CAC5B;AAED,sDAAsD;AACtD,MAAM,WAAW,qBAAqB;IACpC,iEAAiE;IACjE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,sCAAsC;IACtC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,YAAY,GAAG,qBAAqB,GAAG,OAAO,CAAC;CAC1D;AAYD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAGnF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAA;CAAE,CAMhK;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAGzE;AAaD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAgD3G"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Hand remote MCP servers to an agent at launch.
3
+ *
4
+ * For each server whose `mcp:<id>` allowlist admits the agent's role:
5
+ *
6
+ * | Runtime | How |
7
+ * |---|---|
8
+ * | Claude Code | `--mcp-config '<CREWLY_HOME>/runtime/remote-mcp/<session>/claude-mcp.json'` (adds to the project `.mcp.json`; no `--strict-mcp-config`) |
9
+ * | Codex | `-c "mcp_servers.<id>.url=$(cat '<file>')"` (+ `http_headers`), so the secret never appears on the typed command line |
10
+ * | Gemini CLI, Antigravity, OpenCode, crewly-agent | skipped with a log line |
11
+ *
12
+ * Every file is written under CREWLY_HOME with mode 0600 (directory 0700),
13
+ * never into the project. The returned flags carry only file paths.
14
+ *
15
+ * @module services/connector/remote-mcp-launch.service
16
+ */
17
+ import * as fs from 'fs/promises';
18
+ import * as path from 'path';
19
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
20
+ import { LoggerService } from '../core/logger.service.js';
21
+ import { REMOTE_MCP_CONSTANTS, RUNTIME_TYPES } from '../../constants.js';
22
+ import { RemoteMcpService, writeSecretJson } from './remote-mcp.service.js';
23
+ const C = REMOTE_MCP_CONSTANTS;
24
+ /**
25
+ * Quote for a POSIX shell.
26
+ *
27
+ * @param value - Raw value
28
+ * @returns Single-quoted value
29
+ */
30
+ function shq(value) {
31
+ return `'${value.replace(/'/g, `'\\''`)}'`;
32
+ }
33
+ /**
34
+ * The per-session directory for launch files.
35
+ *
36
+ * @param crewlyHome - CREWLY_HOME
37
+ * @param sessionName - PTY session name
38
+ * @returns Absolute directory
39
+ */
40
+ export function remoteMcpSessionDir(crewlyHome, sessionName) {
41
+ const safe = sessionName.replace(/[^A-Za-z0-9._-]/g, '_').replace(/^\.+/, '_') || '_';
42
+ return path.join(crewlyHome, C.RUNTIME_DIR, safe);
43
+ }
44
+ /**
45
+ * Claude Code's `--mcp-config` body.
46
+ *
47
+ * @param servers - Allowed servers
48
+ * @returns `{ mcpServers: { <id>: { type: 'http', url, headers? } } }`
49
+ */
50
+ export function buildClaudeMcpConfig(servers) {
51
+ const mcpServers = {};
52
+ for (const s of servers) {
53
+ mcpServers[s.id] = { type: 'http', url: s.url, ...(s.headers && Object.keys(s.headers).length ? { headers: s.headers } : {}) };
54
+ }
55
+ return { mcpServers };
56
+ }
57
+ /**
58
+ * A TOML inline table of headers (JSON strings are valid TOML basic strings).
59
+ *
60
+ * @param headers - Header map
61
+ * @returns e.g. `{ "Authorization" = "Bearer x" }`
62
+ */
63
+ export function toTomlInlineTable(headers) {
64
+ const parts = Object.entries(headers).map(([k, v]) => `${JSON.stringify(k)} = ${JSON.stringify(v)}`);
65
+ return `{ ${parts.join(', ')} }`;
66
+ }
67
+ /**
68
+ * Write a 0600 text file.
69
+ *
70
+ * @param file - Destination
71
+ * @param body - Content
72
+ */
73
+ async function writeSecretText(file, body) {
74
+ await fs.writeFile(file, body, { encoding: 'utf8', mode: 0o600 });
75
+ await fs.chmod(file, 0o600);
76
+ }
77
+ /**
78
+ * Write the launch files for one agent and return the flags that load them.
79
+ * Never throws: on any failure the agent launches without remote MCP and
80
+ * the error is logged (without secrets).
81
+ *
82
+ * @param input - Session, role, runtime
83
+ * @returns Flags and the server ids handed over
84
+ */
85
+ export async function buildRemoteMcpLaunchFlags(input) {
86
+ const logger = LoggerService.getInstance().createComponentLogger('RemoteMcpLaunch');
87
+ const { sessionName, role, runtimeType } = input;
88
+ const crewlyHome = input.crewlyHome || getCrewlyHomePath();
89
+ const dir = remoteMcpSessionDir(crewlyHome, sessionName);
90
+ try {
91
+ const servers = await (input.service ?? RemoteMcpService.getInstance()).serversForRole(role);
92
+ // Start clean: a server removed or a role narrowed since the last launch must be gone.
93
+ await fs.rm(dir, { recursive: true, force: true });
94
+ if (servers.length === 0)
95
+ return { flags: [], servers: [], skipped: 'no-servers' };
96
+ const ids = servers.map((s) => s.id);
97
+ if (runtimeType !== RUNTIME_TYPES.CLAUDE_CODE && runtimeType !== RUNTIME_TYPES.CODEX_CLI) {
98
+ logger.info('Remote MCP servers: not supported for this runtime — skipped', { sessionName, runtimeType, servers: ids });
99
+ return { flags: [], servers: [], skipped: 'unsupported-runtime' };
100
+ }
101
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
102
+ await fs.chmod(dir, 0o700);
103
+ if (runtimeType === RUNTIME_TYPES.CLAUDE_CODE) {
104
+ const file = path.join(dir, C.CLAUDE_CONFIG_FILE);
105
+ await writeSecretJson(file, buildClaudeMcpConfig(servers));
106
+ logger.info('Remote MCP servers: added via --mcp-config', { sessionName, role, servers: ids });
107
+ return { flags: ['--mcp-config', shq(file)], servers: ids };
108
+ }
109
+ const flags = [];
110
+ for (const s of servers) {
111
+ const urlFile = path.join(dir, `codex-${s.id}-url.toml`);
112
+ await writeSecretText(urlFile, JSON.stringify(s.url));
113
+ flags.push('-c', `"mcp_servers.${s.id}.url=$(cat ${shq(urlFile)})"`);
114
+ if (s.headers && Object.keys(s.headers).length > 0) {
115
+ const headersFile = path.join(dir, `codex-${s.id}-headers.toml`);
116
+ await writeSecretText(headersFile, toTomlInlineTable(s.headers));
117
+ flags.push('-c', `"mcp_servers.${s.id}.http_headers=$(cat ${shq(headersFile)})"`);
118
+ }
119
+ }
120
+ logger.info('Remote MCP servers: added via codex -c mcp_servers', { sessionName, role, servers: ids });
121
+ return { flags, servers: ids };
122
+ }
123
+ catch (err) {
124
+ logger.error('Remote MCP servers: could not prepare launch files — launching without them', {
125
+ sessionName,
126
+ runtimeType,
127
+ error: err instanceof Error ? err.message : String(err),
128
+ });
129
+ return { flags: [], servers: [], skipped: 'error' };
130
+ }
131
+ }
132
+ //# sourceMappingURL=remote-mcp-launch.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-mcp-launch.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/remote-mcp-launch.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Remote MCP servers — streamable-HTTP MCP servers the owner connects once
3
+ * (Zoho MCP first, any other server the same way) and Crewly hands to every
4
+ * agent whose role may use them.
5
+ *
6
+ * Stored at `<CREWLY_HOME>/remote-mcp-servers.json` with mode 0600. The URL
7
+ * (Zoho's carries the server's API key) and any headers are secrets: they
8
+ * are never logged, never returned in full by {@link toView}, and never
9
+ * written into a project directory (a project `.mcp.json` would be
10
+ * committed). Agents get them through per-session files under CREWLY_HOME
11
+ * (see remote-mcp-launch.service).
12
+ *
13
+ * Each server is a gated connector `mcp:<id>` in
14
+ * {@link ConnectorAccessService}: open to every agent until the owner picks
15
+ * roles.
16
+ *
17
+ * @module services/connector/remote-mcp.service
18
+ */
19
+ import { ConnectorAccessService } from './connector-access.service.js';
20
+ /** Catalog preset a server was added from. */
21
+ export type RemoteMcpProvider = 'zoho' | 'custom';
22
+ /** One stored server (secrets included — never send this to a client). */
23
+ export interface RemoteMcpServer {
24
+ /** Stable id; also the MCP server name agents see and the `mcp:<id>` access key. */
25
+ id: string;
26
+ /** Owner-facing name. */
27
+ label: string;
28
+ /** Streamable-HTTP endpoint (secret). */
29
+ url: string;
30
+ /** Extra request headers (secret values). */
31
+ headers?: Record<string, string>;
32
+ /** Catalog preset. */
33
+ provider?: RemoteMcpProvider;
34
+ /** ISO time it was added. */
35
+ createdAt: string;
36
+ }
37
+ /** What the API returns: no secrets. */
38
+ export interface RemoteMcpServerView {
39
+ id: string;
40
+ label: string;
41
+ provider: RemoteMcpProvider;
42
+ /** Scheme + host only, e.g. `https://crm-123.zohomcp.com/…`. */
43
+ urlMasked: string;
44
+ /** Header names (values withheld). */
45
+ headerNames: string[];
46
+ createdAt: string;
47
+ /** Key of its role allowlist in connector access. */
48
+ connectorId: string;
49
+ }
50
+ /** Input for {@link RemoteMcpService.add}. */
51
+ export interface AddRemoteMcpInput {
52
+ label: string;
53
+ url: string;
54
+ headers?: Record<string, string>;
55
+ provider?: string;
56
+ }
57
+ /** A validation failure the API turns into a 400. */
58
+ export declare class RemoteMcpValidationError extends Error {
59
+ constructor(message: string);
60
+ }
61
+ /**
62
+ * The connector-access id of a server.
63
+ *
64
+ * @param id - Server id
65
+ * @returns `mcp:<id>`
66
+ */
67
+ export declare function remoteMcpConnectorId(id: string): string;
68
+ /**
69
+ * Show a URL without its secret parts: scheme and host only.
70
+ *
71
+ * @param url - Stored URL
72
+ * @returns e.g. `https://crm-123.zohomcp.com/…`, or `(hidden)` when it does not parse
73
+ */
74
+ export declare function maskRemoteMcpUrl(url: string): string;
75
+ /**
76
+ * Validate a server URL: https (http only for localhost), no whitespace,
77
+ * quotes or backslashes (it is embedded in launch files), bounded length.
78
+ *
79
+ * @param raw - Pasted URL
80
+ * @returns The trimmed URL
81
+ * @throws RemoteMcpValidationError when it is not usable (message never echoes the URL)
82
+ */
83
+ export declare function validateRemoteMcpUrl(raw: unknown): string;
84
+ /**
85
+ * Validate optional headers: token-shaped names, single-line values.
86
+ *
87
+ * @param raw - Headers object or undefined
88
+ * @returns Clean headers, or undefined when none
89
+ * @throws RemoteMcpValidationError on a bad name or value
90
+ */
91
+ export declare function validateRemoteMcpHeaders(raw: unknown): Record<string, string> | undefined;
92
+ /**
93
+ * Clean a label.
94
+ *
95
+ * @param raw - Label input
96
+ * @returns Trimmed label
97
+ * @throws RemoteMcpValidationError when empty
98
+ */
99
+ export declare function normalizeRemoteMcpLabel(raw: unknown): string;
100
+ /**
101
+ * Derive an id (MCP server name) from a label: lowercase slug, unique.
102
+ *
103
+ * @param label - Label
104
+ * @param taken - Ids already in use
105
+ * @returns e.g. `zoho`, `zoho-2`
106
+ */
107
+ export declare function deriveRemoteMcpId(label: string, taken: Set<string>): string;
108
+ /**
109
+ * Turn a stored server into its API shape.
110
+ *
111
+ * @param server - Stored server
112
+ * @returns The view (no URL, no header values)
113
+ */
114
+ export declare function toView(server: RemoteMcpServer): RemoteMcpServerView;
115
+ /**
116
+ * Reads and writes the remote MCP server store.
117
+ */
118
+ export declare class RemoteMcpService {
119
+ private static instance;
120
+ private readonly logger;
121
+ private readonly filePath;
122
+ private readonly access;
123
+ private writeChain;
124
+ /**
125
+ * @param crewlyHome - Override the home directory (tests)
126
+ * @param access - Connector access service (tests)
127
+ */
128
+ constructor(crewlyHome?: string, access?: ConnectorAccessService);
129
+ static getInstance(): RemoteMcpService;
130
+ /** Reset the singleton (tests). */
131
+ static resetInstance(): void;
132
+ /** Absolute path of the store file. */
133
+ getFilePath(): string;
134
+ /**
135
+ * Every stored server, with secrets. Unreadable entries are dropped.
136
+ *
137
+ * @returns Servers in the order they were added
138
+ */
139
+ list(): Promise<RemoteMcpServer[]>;
140
+ /**
141
+ * One server, with secrets.
142
+ *
143
+ * @param id - Server id
144
+ * @returns The server or undefined
145
+ */
146
+ get(id: string): Promise<RemoteMcpServer | undefined>;
147
+ /**
148
+ * Add a server.
149
+ *
150
+ * @param input - Label, URL, optional headers and provider
151
+ * @returns The stored server
152
+ * @throws RemoteMcpValidationError on bad input or a full store
153
+ */
154
+ add(input: AddRemoteMcpInput): Promise<RemoteMcpServer>;
155
+ /**
156
+ * Rename a server (its id, and so its MCP name and allowlist, stay).
157
+ *
158
+ * @param id - Server id
159
+ * @param label - New label
160
+ * @returns The updated server, or undefined when there is none
161
+ */
162
+ rename(id: string, label: string): Promise<RemoteMcpServer | undefined>;
163
+ /**
164
+ * Remove a server and its allowlist.
165
+ *
166
+ * @param id - Server id
167
+ * @returns True when it existed
168
+ */
169
+ remove(id: string): Promise<boolean>;
170
+ /**
171
+ * Servers an agent role may use (its `mcp:<id>` allowlist passes).
172
+ *
173
+ * @param role - Agent role
174
+ * @returns Allowed servers, with secrets
175
+ */
176
+ serversForRole(role: string): Promise<RemoteMcpServer[]>;
177
+ /**
178
+ * Serialised read-modify-write; the file is written 0600.
179
+ *
180
+ * @param fn - Change to apply
181
+ * @returns The change's result
182
+ */
183
+ private mutate;
184
+ }
185
+ /**
186
+ * Write JSON readable only by the owner (0600), atomically.
187
+ *
188
+ * @param filePath - Destination
189
+ * @param data - JSON value
190
+ */
191
+ export declare function writeSecretJson(filePath: string, data: unknown): Promise<void>;
192
+ //# sourceMappingURL=remote-mcp.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-mcp.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/remote-mcp.service.ts"],"names":[],"mappings":"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"}