crewly 1.20.247 → 1.20.249
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/config/skills/agent/core/list-channels/SKILL.md +61 -0
- package/config/skills/agent/core/list-channels/execute.sh +36 -0
- package/config/skills/agent/core/list-channels/execute.test.sh +17 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +10 -0
- package/config/skills/agent/core/reply-channel/execute.sh +23 -1
- package/config/skills/agent/core/reply-channel/execute.test.sh +25 -0
- package/dist/backend/backend/src/constants.d.ts +56 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +56 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/channels/channels.routes.d.ts +27 -0
- package/dist/backend/backend/src/controllers/channels/channels.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/channels/channels.routes.js +99 -0
- package/dist/backend/backend/src/controllers/channels/channels.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts +1 -0
- package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.controller.js +7 -0
- package/dist/backend/backend/src/controllers/connector/connector.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts +1 -0
- package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/connector/connector.routes.js +7 -0
- package/dist/backend/backend/src/controllers/connector/connector.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts +55 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js +148 -0
- package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js.map +1 -0
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +18 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +5 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/channels/crewly-channel.service.d.ts +247 -0
- package/dist/backend/backend/src/services/channels/crewly-channel.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/channels/crewly-channel.service.js +631 -0
- package/dist/backend/backend/src/services/channels/crewly-channel.service.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +26 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +40 -2
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/backend/backend/src/services/connector/connector-access.service.d.ts +8 -0
- package/dist/backend/backend/src/services/connector/connector-access.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/connector/connector-access.service.js +17 -0
- package/dist/backend/backend/src/services/connector/connector-access.service.js.map +1 -1
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts +77 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js +132 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts +75 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js +155 -0
- package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp.service.d.ts +192 -0
- package/dist/backend/backend/src/services/connector/remote-mcp.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/connector/remote-mcp.service.js +334 -0
- package/dist/backend/backend/src/services/connector/remote-mcp.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/credential-files.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/credential-files.js +4 -1
- package/dist/backend/backend/src/services/core/credential-files.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-channel-members.d.ts +60 -0
- package/dist/backend/backend/src/services/slack/slack-channel-members.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-channel-members.js +70 -0
- package/dist/backend/backend/src/services/slack/slack-channel-members.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +147 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +423 -12
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +10 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +15 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/types/crewly-channel.types.d.ts +77 -0
- package/dist/backend/backend/src/types/crewly-channel.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/crewly-channel.types.js +35 -0
- package/dist/backend/backend/src/types/crewly-channel.types.js.map +1 -0
- package/dist/backend/backend/src/types/slack.types.d.ts +7 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +56 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +56 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +5 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +26 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +40 -2
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/cli/backend/src/services/connector/connector-access.service.d.ts +85 -0
- package/dist/cli/backend/src/services/connector/connector-access.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/connector/connector-access.service.js +130 -0
- package/dist/cli/backend/src/services/connector/connector-access.service.js.map +1 -0
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts +77 -0
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js +132 -0
- package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -0
- package/dist/cli/backend/src/services/connector/remote-mcp.service.d.ts +192 -0
- package/dist/cli/backend/src/services/connector/remote-mcp.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/connector/remote-mcp.service.js +334 -0
- package/dist/cli/backend/src/services/connector/remote-mcp.service.js.map +1 -0
- package/dist/cli/backend/src/services/core/credential-files.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/credential-files.js +4 -1
- package/dist/cli/backend/src/services/core/credential-files.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-channel-members.d.ts +87 -0
- package/dist/cli/backend/src/services/slack/slack-channel-members.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-channel-members.js +124 -0
- package/dist/cli/backend/src/services/slack/slack-channel-members.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +147 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +423 -12
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +10 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +15 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +7 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/frontend/dist/assets/{index-c9630c55.js → index-2db17f1e.js} +292 -292
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
|
@@ -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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"}
|