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.
Files changed (132) hide show
  1. package/config/skills/agent/core/list-channels/SKILL.md +61 -0
  2. package/config/skills/agent/core/list-channels/execute.sh +36 -0
  3. package/config/skills/agent/core/list-channels/execute.test.sh +17 -0
  4. package/config/skills/agent/core/reply-channel/SKILL.md +10 -0
  5. package/config/skills/agent/core/reply-channel/execute.sh +23 -1
  6. package/config/skills/agent/core/reply-channel/execute.test.sh +25 -0
  7. package/dist/backend/backend/src/constants.d.ts +56 -0
  8. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  9. package/dist/backend/backend/src/constants.js +56 -0
  10. package/dist/backend/backend/src/constants.js.map +1 -1
  11. package/dist/backend/backend/src/controllers/channels/channels.routes.d.ts +27 -0
  12. package/dist/backend/backend/src/controllers/channels/channels.routes.d.ts.map +1 -0
  13. package/dist/backend/backend/src/controllers/channels/channels.routes.js +99 -0
  14. package/dist/backend/backend/src/controllers/channels/channels.routes.js.map +1 -0
  15. package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts +1 -0
  16. package/dist/backend/backend/src/controllers/connector/connector.controller.d.ts.map +1 -1
  17. package/dist/backend/backend/src/controllers/connector/connector.controller.js +7 -0
  18. package/dist/backend/backend/src/controllers/connector/connector.controller.js.map +1 -1
  19. package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts +1 -0
  20. package/dist/backend/backend/src/controllers/connector/connector.routes.d.ts.map +1 -1
  21. package/dist/backend/backend/src/controllers/connector/connector.routes.js +7 -0
  22. package/dist/backend/backend/src/controllers/connector/connector.routes.js.map +1 -1
  23. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts +55 -0
  24. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.d.ts.map +1 -0
  25. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js +148 -0
  26. package/dist/backend/backend/src/controllers/connector/remote-mcp.controller.js.map +1 -0
  27. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  28. package/dist/backend/backend/src/routes/api.routes.js +18 -0
  29. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  30. package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  31. package/dist/backend/backend/src/services/agent/agent-registration.service.js +5 -0
  32. package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
  33. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  34. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  35. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  36. package/dist/backend/backend/src/services/channels/crewly-channel.service.d.ts +247 -0
  37. package/dist/backend/backend/src/services/channels/crewly-channel.service.d.ts.map +1 -0
  38. package/dist/backend/backend/src/services/channels/crewly-channel.service.js +631 -0
  39. package/dist/backend/backend/src/services/channels/crewly-channel.service.js.map +1 -0
  40. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +26 -0
  41. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
  42. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +40 -2
  43. package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
  44. package/dist/backend/backend/src/services/connector/connector-access.service.d.ts +8 -0
  45. package/dist/backend/backend/src/services/connector/connector-access.service.d.ts.map +1 -1
  46. package/dist/backend/backend/src/services/connector/connector-access.service.js +17 -0
  47. package/dist/backend/backend/src/services/connector/connector-access.service.js.map +1 -1
  48. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts +77 -0
  49. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -0
  50. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js +132 -0
  51. package/dist/backend/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -0
  52. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts +75 -0
  53. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.d.ts.map +1 -0
  54. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js +155 -0
  55. package/dist/backend/backend/src/services/connector/remote-mcp-probe.service.js.map +1 -0
  56. package/dist/backend/backend/src/services/connector/remote-mcp.service.d.ts +192 -0
  57. package/dist/backend/backend/src/services/connector/remote-mcp.service.d.ts.map +1 -0
  58. package/dist/backend/backend/src/services/connector/remote-mcp.service.js +334 -0
  59. package/dist/backend/backend/src/services/connector/remote-mcp.service.js.map +1 -0
  60. package/dist/backend/backend/src/services/core/credential-files.d.ts.map +1 -1
  61. package/dist/backend/backend/src/services/core/credential-files.js +4 -1
  62. package/dist/backend/backend/src/services/core/credential-files.js.map +1 -1
  63. package/dist/backend/backend/src/services/slack/slack-channel-members.d.ts +60 -0
  64. package/dist/backend/backend/src/services/slack/slack-channel-members.d.ts.map +1 -1
  65. package/dist/backend/backend/src/services/slack/slack-channel-members.js +70 -0
  66. package/dist/backend/backend/src/services/slack/slack-channel-members.js.map +1 -1
  67. package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
  68. package/dist/backend/backend/src/services/slack/slack-initializer.js +2 -1
  69. package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
  70. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +147 -1
  71. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  72. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +423 -12
  73. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  74. package/dist/backend/backend/src/services/slack/slack.service.d.ts +10 -0
  75. package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/slack/slack.service.js +15 -0
  77. package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
  78. package/dist/backend/backend/src/types/crewly-channel.types.d.ts +77 -0
  79. package/dist/backend/backend/src/types/crewly-channel.types.d.ts.map +1 -0
  80. package/dist/backend/backend/src/types/crewly-channel.types.js +35 -0
  81. package/dist/backend/backend/src/types/crewly-channel.types.js.map +1 -0
  82. package/dist/backend/backend/src/types/slack.types.d.ts +7 -0
  83. package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
  84. package/dist/backend/backend/src/types/slack.types.js.map +1 -1
  85. package/dist/backend/build-info.json +2 -2
  86. package/dist/cli/backend/src/constants.d.ts +56 -0
  87. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  88. package/dist/cli/backend/src/constants.js +56 -0
  89. package/dist/cli/backend/src/constants.js.map +1 -1
  90. package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
  91. package/dist/cli/backend/src/services/agent/agent-registration.service.js +5 -0
  92. package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
  93. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  94. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
  95. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  96. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +26 -0
  97. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
  98. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +40 -2
  99. package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
  100. package/dist/cli/backend/src/services/connector/connector-access.service.d.ts +85 -0
  101. package/dist/cli/backend/src/services/connector/connector-access.service.d.ts.map +1 -0
  102. package/dist/cli/backend/src/services/connector/connector-access.service.js +130 -0
  103. package/dist/cli/backend/src/services/connector/connector-access.service.js.map +1 -0
  104. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts +77 -0
  105. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.d.ts.map +1 -0
  106. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js +132 -0
  107. package/dist/cli/backend/src/services/connector/remote-mcp-launch.service.js.map +1 -0
  108. package/dist/cli/backend/src/services/connector/remote-mcp.service.d.ts +192 -0
  109. package/dist/cli/backend/src/services/connector/remote-mcp.service.d.ts.map +1 -0
  110. package/dist/cli/backend/src/services/connector/remote-mcp.service.js +334 -0
  111. package/dist/cli/backend/src/services/connector/remote-mcp.service.js.map +1 -0
  112. package/dist/cli/backend/src/services/core/credential-files.d.ts.map +1 -1
  113. package/dist/cli/backend/src/services/core/credential-files.js +4 -1
  114. package/dist/cli/backend/src/services/core/credential-files.js.map +1 -1
  115. package/dist/cli/backend/src/services/slack/slack-channel-members.d.ts +87 -0
  116. package/dist/cli/backend/src/services/slack/slack-channel-members.d.ts.map +1 -0
  117. package/dist/cli/backend/src/services/slack/slack-channel-members.js +124 -0
  118. package/dist/cli/backend/src/services/slack/slack-channel-members.js.map +1 -0
  119. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +147 -1
  120. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  121. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +423 -12
  122. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  123. package/dist/cli/backend/src/services/slack/slack.service.d.ts +10 -0
  124. package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
  125. package/dist/cli/backend/src/services/slack/slack.service.js +15 -0
  126. package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
  127. package/dist/cli/backend/src/types/slack.types.d.ts +7 -0
  128. package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
  129. package/dist/cli/backend/src/types/slack.types.js.map +1 -1
  130. package/frontend/dist/assets/{index-c9630c55.js → index-2db17f1e.js} +292 -292
  131. package/frontend/dist/index.html +1 -1
  132. package/package.json +1 -1
@@ -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,75 @@
1
+ /**
2
+ * "Test" for a remote MCP server: MCP `initialize`, `notifications/initialized`
3
+ * and `tools/list` over streamable HTTP, reporting the tool count and names.
4
+ *
5
+ * Handles both response shapes the transport allows (a JSON body or a
6
+ * `text/event-stream` with the reply in a `data:` line) and carries the
7
+ * `Mcp-Session-Id` the server hands out. Never puts the URL (or anything of
8
+ * its path) into a result or error.
9
+ *
10
+ * @module services/connector/remote-mcp-probe.service
11
+ */
12
+ import type { RemoteMcpServer } from './remote-mcp.service.js';
13
+ /** Result of a probe. */
14
+ export type RemoteMcpProbeResult = {
15
+ ok: true;
16
+ serverName?: string;
17
+ toolCount: number;
18
+ tools: string[];
19
+ } | {
20
+ ok: false;
21
+ error: string;
22
+ };
23
+ /** Minimal fetch signature (injectable for tests). */
24
+ export type FetchLike = (url: string, init: {
25
+ method: string;
26
+ headers: Record<string, string>;
27
+ body?: string;
28
+ signal?: AbortSignal;
29
+ }) => Promise<{
30
+ status: number;
31
+ ok: boolean;
32
+ headers: {
33
+ get(name: string): string | null;
34
+ };
35
+ text(): Promise<string>;
36
+ }>;
37
+ /** A JSON-RPC response. */
38
+ interface JsonRpcResponse {
39
+ jsonrpc?: string;
40
+ id?: number | string | null;
41
+ result?: Record<string, unknown>;
42
+ error?: {
43
+ code?: number;
44
+ message?: string;
45
+ };
46
+ }
47
+ /**
48
+ * Remove every trace of the URL from a message: the whole URL, its path,
49
+ * and any long path segment (Zoho's key is one).
50
+ *
51
+ * @param message - Raw message
52
+ * @param url - Server URL
53
+ * @returns Safe message
54
+ */
55
+ export declare function scrubUrl(message: string, url: string): string;
56
+ /**
57
+ * Read a JSON-RPC reply out of a JSON or SSE body.
58
+ *
59
+ * @param contentType - Response content type
60
+ * @param body - Response text
61
+ * @param id - Request id to match
62
+ * @returns The reply, or undefined
63
+ */
64
+ export declare function parseRpcBody(contentType: string, body: string, id: number): JsonRpcResponse | undefined;
65
+ /**
66
+ * Probe a server: initialize, then list its tools (following `nextCursor`
67
+ * up to five pages).
68
+ *
69
+ * @param server - Stored server (URL and headers)
70
+ * @param fetchImpl - fetch (tests)
71
+ * @returns Tool count and names, or a safe error
72
+ */
73
+ export declare function probeRemoteMcp(server: Pick<RemoteMcpServer, 'url' | 'headers'>, fetchImpl?: FetchLike): Promise<RemoteMcpProbeResult>;
74
+ export {};
75
+ //# sourceMappingURL=remote-mcp-probe.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-mcp-probe.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/remote-mcp-probe.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,yBAAyB;AACzB,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,GACrE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjC,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,KAAK,OAAO,CAAC;IAC/I,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB,CAAC,CAAC;AAEH,2BAA2B;AAC3B,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAW7D;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAsBvG;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,eAAe,EAAE,KAAK,GAAG,SAAS,CAAC,EAAE,SAAS,GAAE,SAAyC,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiE1K"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * "Test" for a remote MCP server: MCP `initialize`, `notifications/initialized`
3
+ * and `tools/list` over streamable HTTP, reporting the tool count and names.
4
+ *
5
+ * Handles both response shapes the transport allows (a JSON body or a
6
+ * `text/event-stream` with the reply in a `data:` line) and carries the
7
+ * `Mcp-Session-Id` the server hands out. Never puts the URL (or anything of
8
+ * its path) into a result or error.
9
+ *
10
+ * @module services/connector/remote-mcp-probe.service
11
+ */
12
+ import { REMOTE_MCP_CONSTANTS } from '../../constants.js';
13
+ const C = REMOTE_MCP_CONSTANTS;
14
+ /**
15
+ * Remove every trace of the URL from a message: the whole URL, its path,
16
+ * and any long path segment (Zoho's key is one).
17
+ *
18
+ * @param message - Raw message
19
+ * @param url - Server URL
20
+ * @returns Safe message
21
+ */
22
+ export function scrubUrl(message, url) {
23
+ let out = message.split(url).join('(server URL)');
24
+ try {
25
+ const u = new URL(url);
26
+ if (u.pathname.length > 1)
27
+ out = out.split(u.pathname).join('/…');
28
+ for (const seg of u.pathname.split('/').filter((s) => s.length >= 8))
29
+ out = out.split(seg).join('…');
30
+ if (u.search)
31
+ out = out.split(u.search).join('?…');
32
+ }
33
+ catch {
34
+ /* unparseable URL: whole-string removal above is all we can do */
35
+ }
36
+ return out;
37
+ }
38
+ /**
39
+ * Read a JSON-RPC reply out of a JSON or SSE body.
40
+ *
41
+ * @param contentType - Response content type
42
+ * @param body - Response text
43
+ * @param id - Request id to match
44
+ * @returns The reply, or undefined
45
+ */
46
+ export function parseRpcBody(contentType, body, id) {
47
+ const candidates = [];
48
+ if (contentType.includes('text/event-stream')) {
49
+ // Events are separated by blank lines; a data field may span several lines.
50
+ for (const event of body.split(/\r?\n\r?\n/)) {
51
+ const data = event.split(/\r?\n/).filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trimStart()).join('\n');
52
+ if (!data)
53
+ continue;
54
+ try {
55
+ candidates.push(JSON.parse(data));
56
+ }
57
+ catch {
58
+ /* not JSON — skip */
59
+ }
60
+ }
61
+ }
62
+ else {
63
+ try {
64
+ candidates.push(JSON.parse(body));
65
+ }
66
+ catch {
67
+ return undefined;
68
+ }
69
+ }
70
+ const flat = candidates.flatMap((c) => (Array.isArray(c) ? c : [c]));
71
+ return flat.find((m) => m && m.id === id);
72
+ }
73
+ /**
74
+ * Probe a server: initialize, then list its tools (following `nextCursor`
75
+ * up to five pages).
76
+ *
77
+ * @param server - Stored server (URL and headers)
78
+ * @param fetchImpl - fetch (tests)
79
+ * @returns Tool count and names, or a safe error
80
+ */
81
+ export async function probeRemoteMcp(server, fetchImpl = fetch) {
82
+ const { url } = server;
83
+ let sessionId = null;
84
+ let protocolVersion = C.PROTOCOL_VERSION;
85
+ const post = async (payload) => {
86
+ const headers = {
87
+ ...(server.headers ?? {}),
88
+ 'Content-Type': 'application/json',
89
+ Accept: 'application/json, text/event-stream',
90
+ };
91
+ if (sessionId)
92
+ headers['Mcp-Session-Id'] = sessionId;
93
+ if (payload.method !== 'initialize')
94
+ headers['MCP-Protocol-Version'] = protocolVersion;
95
+ const res = await fetchImpl(url, { method: 'POST', headers, body: JSON.stringify(payload), signal: AbortSignal.timeout(C.TEST_TIMEOUT_MS) });
96
+ const sid = res.headers.get('mcp-session-id');
97
+ if (sid)
98
+ sessionId = sid;
99
+ return { status: res.status, contentType: res.headers.get('content-type') ?? '', body: await res.text() };
100
+ };
101
+ const call = async (id, method, params) => {
102
+ const res = await post({ jsonrpc: '2.0', id, method, params });
103
+ if (res.status === 401 || res.status === 403) {
104
+ throw new Error(`The server refused the request (${res.status}). Check the URL — it may have been regenerated.`);
105
+ }
106
+ if (res.status === 404)
107
+ throw new Error('The server was not found (404). Check the URL.');
108
+ if (res.status < 200 || res.status >= 300)
109
+ throw new Error(`The server answered ${res.status} to ${method}.`);
110
+ const reply = parseRpcBody(res.contentType, res.body, id);
111
+ if (!reply)
112
+ throw new Error(`The server's reply to ${method} was not MCP (is this an MCP server URL?).`);
113
+ if (reply.error)
114
+ throw new Error(`The server returned an error for ${method}: ${String(reply.error.message ?? reply.error.code ?? 'unknown')}`);
115
+ return reply.result ?? {};
116
+ };
117
+ try {
118
+ const init = await call(1, 'initialize', {
119
+ protocolVersion: C.PROTOCOL_VERSION,
120
+ capabilities: {},
121
+ clientInfo: { name: 'crewly', version: '1.0.0' },
122
+ });
123
+ if (typeof init.protocolVersion === 'string')
124
+ protocolVersion = init.protocolVersion;
125
+ const serverInfo = init.serverInfo;
126
+ // Notifications get 202 and no body; a failure here is not fatal.
127
+ await post({ jsonrpc: '2.0', method: 'notifications/initialized' }).catch(() => undefined);
128
+ const names = [];
129
+ let cursor;
130
+ for (let page = 0; page < 5; page++) {
131
+ const result = await call(2 + page, 'tools/list', cursor ? { cursor } : {});
132
+ const tools = Array.isArray(result.tools) ? result.tools : [];
133
+ for (const t of tools)
134
+ if (typeof t?.name === 'string')
135
+ names.push(t.name);
136
+ cursor = typeof result.nextCursor === 'string' && result.nextCursor ? result.nextCursor : undefined;
137
+ if (!cursor)
138
+ break;
139
+ }
140
+ return {
141
+ ok: true,
142
+ ...(typeof serverInfo?.name === 'string' ? { serverName: scrubUrl(serverInfo.name, url) } : {}),
143
+ toolCount: names.length,
144
+ tools: names.slice(0, C.MAX_TOOL_NAMES),
145
+ };
146
+ }
147
+ catch (err) {
148
+ const raw = err instanceof Error
149
+ ? (err.name === 'TimeoutError' || err.name === 'AbortError' ? 'The server did not answer in time.' : err.message)
150
+ : String(err);
151
+ const cause = err instanceof Error && err.cause instanceof Error ? ` (${err.cause.message})` : '';
152
+ return { ok: false, error: scrubUrl(`${raw}${cause}`, url) };
153
+ }
154
+ }
155
+ //# sourceMappingURL=remote-mcp-probe.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-mcp-probe.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/connector/remote-mcp-probe.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":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAOH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAIvE,8CAA8C;AAC9C,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAElD,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,oFAAoF;IACpF,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,sBAAsB;IACtB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qDAAqD;AACrD,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAkBzD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAYzF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAI5D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,CAM3E;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,mBAAmB,CAUnE;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAiC;IACxD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IACtD,OAAO,CAAC,UAAU,CAAuC;IAEzD;;;OAGG;gBACS,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,sBAAsB;IAMhE,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAKtC,mCAAmC;IACnC,MAAM,CAAC,aAAa,IAAI,IAAI;IAI5B,uCAAuC;IACvC,WAAW,IAAI,MAAM;IAIrB;;;;OAIG;IACG,IAAI,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAqBxC;;;;;OAKG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAI3D;;;;;;OAMG;IACG,GAAG,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAe7D;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAY7E;;;;;OAKG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAY1C;;;;;OAKG;IACG,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAU9D;;;;;OAKG;YACW,MAAM;CASrB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpF"}