cowork-os 0.3.21 → 0.3.23

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 (170) hide show
  1. package/README.md +293 -6
  2. package/connectors/README.md +20 -0
  3. package/connectors/asana-mcp/README.md +24 -0
  4. package/connectors/asana-mcp/dist/index.js +427 -0
  5. package/connectors/asana-mcp/package.json +15 -0
  6. package/connectors/asana-mcp/src/index.ts +553 -0
  7. package/connectors/asana-mcp/tsconfig.json +13 -0
  8. package/connectors/hubspot-mcp/README.md +35 -0
  9. package/connectors/hubspot-mcp/dist/index.js +454 -0
  10. package/connectors/hubspot-mcp/package.json +15 -0
  11. package/connectors/hubspot-mcp/src/index.ts +562 -0
  12. package/connectors/hubspot-mcp/tsconfig.json +13 -0
  13. package/connectors/jira-mcp/README.md +49 -0
  14. package/connectors/jira-mcp/dist/index.js +588 -0
  15. package/connectors/jira-mcp/package.json +15 -0
  16. package/connectors/jira-mcp/src/index.ts +711 -0
  17. package/connectors/jira-mcp/tsconfig.json +13 -0
  18. package/connectors/linear-mcp/README.md +22 -0
  19. package/connectors/linear-mcp/dist/index.js +402 -0
  20. package/connectors/linear-mcp/package.json +15 -0
  21. package/connectors/linear-mcp/src/index.ts +522 -0
  22. package/connectors/linear-mcp/tsconfig.json +13 -0
  23. package/connectors/okta-mcp/README.md +24 -0
  24. package/connectors/okta-mcp/dist/index.js +411 -0
  25. package/connectors/okta-mcp/package.json +15 -0
  26. package/connectors/okta-mcp/src/index.ts +520 -0
  27. package/connectors/okta-mcp/tsconfig.json +13 -0
  28. package/connectors/salesforce-mcp/README.md +47 -0
  29. package/connectors/salesforce-mcp/dist/index.js +584 -0
  30. package/connectors/salesforce-mcp/package.json +15 -0
  31. package/connectors/salesforce-mcp/src/index.ts +722 -0
  32. package/connectors/salesforce-mcp/tsconfig.json +13 -0
  33. package/connectors/servicenow-mcp/README.md +26 -0
  34. package/connectors/servicenow-mcp/dist/index.js +400 -0
  35. package/connectors/servicenow-mcp/package.json +15 -0
  36. package/connectors/servicenow-mcp/src/index.ts +500 -0
  37. package/connectors/servicenow-mcp/tsconfig.json +13 -0
  38. package/connectors/templates/mcp-connector/README.md +31 -0
  39. package/connectors/templates/mcp-connector/package.json +15 -0
  40. package/connectors/templates/mcp-connector/src/index.ts +330 -0
  41. package/connectors/templates/mcp-connector/tsconfig.json +13 -0
  42. package/connectors/zendesk-mcp/README.md +40 -0
  43. package/connectors/zendesk-mcp/dist/index.js +431 -0
  44. package/connectors/zendesk-mcp/package.json +15 -0
  45. package/connectors/zendesk-mcp/src/index.ts +543 -0
  46. package/connectors/zendesk-mcp/tsconfig.json +13 -0
  47. package/dist/electron/electron/agent/daemon.js +25 -0
  48. package/dist/electron/electron/agent/executor.js +181 -26
  49. package/dist/electron/electron/agent/llm/anthropic-compatible-provider.js +177 -0
  50. package/dist/electron/electron/agent/llm/github-copilot-provider.js +97 -0
  51. package/dist/electron/electron/agent/llm/groq-provider.js +33 -0
  52. package/dist/electron/electron/agent/llm/index.js +11 -1
  53. package/dist/electron/electron/agent/llm/kimi-provider.js +33 -0
  54. package/dist/electron/electron/agent/llm/openai-compatible-provider.js +116 -0
  55. package/dist/electron/electron/agent/llm/openai-compatible.js +111 -0
  56. package/dist/electron/electron/agent/llm/openai-oauth.js +2 -1
  57. package/dist/electron/electron/agent/llm/openrouter-provider.js +1 -1
  58. package/dist/electron/electron/agent/llm/provider-factory.js +318 -4
  59. package/dist/electron/electron/agent/llm/types.js +66 -1
  60. package/dist/electron/electron/agent/llm/xai-provider.js +33 -0
  61. package/dist/electron/electron/agent/tools/box-tools.js +231 -0
  62. package/dist/electron/electron/agent/tools/builtin-settings.js +28 -0
  63. package/dist/electron/electron/agent/tools/dropbox-tools.js +237 -0
  64. package/dist/electron/electron/agent/tools/google-drive-tools.js +227 -0
  65. package/dist/electron/electron/agent/tools/notion-tools.js +312 -0
  66. package/dist/electron/electron/agent/tools/onedrive-tools.js +217 -0
  67. package/dist/electron/electron/agent/tools/registry.js +541 -0
  68. package/dist/electron/electron/agent/tools/sharepoint-tools.js +243 -0
  69. package/dist/electron/electron/agent/tools/shell-tools.js +12 -3
  70. package/dist/electron/electron/agent/tools/x-tools.js +1 -1
  71. package/dist/electron/electron/gateway/index.js +1 -0
  72. package/dist/electron/electron/gateway/router.js +123 -143
  73. package/dist/electron/electron/ipc/canvas-handlers.js +5 -0
  74. package/dist/electron/electron/ipc/handlers.js +627 -158
  75. package/dist/electron/electron/main.js +63 -0
  76. package/dist/electron/electron/mcp/oauth/connector-oauth.js +333 -0
  77. package/dist/electron/electron/mcp/registry/MCPRegistryManager.js +503 -154
  78. package/dist/electron/electron/memory/MemoryService.js +1 -1
  79. package/dist/electron/electron/preload.js +74 -1
  80. package/dist/electron/electron/settings/box-manager.js +54 -0
  81. package/dist/electron/electron/settings/dropbox-manager.js +54 -0
  82. package/dist/electron/electron/settings/google-drive-manager.js +54 -0
  83. package/dist/electron/electron/settings/notion-manager.js +56 -0
  84. package/dist/electron/electron/settings/onedrive-manager.js +54 -0
  85. package/dist/electron/electron/settings/sharepoint-manager.js +54 -0
  86. package/dist/electron/electron/utils/box-api.js +153 -0
  87. package/dist/electron/electron/utils/dropbox-api.js +144 -0
  88. package/dist/electron/electron/utils/env-migration.js +19 -0
  89. package/dist/electron/electron/utils/google-drive-api.js +152 -0
  90. package/dist/electron/electron/utils/notion-api.js +103 -0
  91. package/dist/electron/electron/utils/onedrive-api.js +113 -0
  92. package/dist/electron/electron/utils/sharepoint-api.js +109 -0
  93. package/dist/electron/electron/utils/validation.js +82 -3
  94. package/dist/electron/electron/utils/x-cli.js +1 -1
  95. package/dist/electron/shared/channelMessages.js +284 -3
  96. package/dist/electron/shared/llm-provider-catalog.js +198 -0
  97. package/dist/electron/shared/types.js +88 -1
  98. package/package.json +12 -2
  99. package/src/electron/agent/executor.ts +205 -28
  100. package/src/electron/agent/llm/anthropic-compatible-provider.ts +214 -0
  101. package/src/electron/agent/llm/github-copilot-provider.ts +117 -0
  102. package/src/electron/agent/llm/groq-provider.ts +39 -0
  103. package/src/electron/agent/llm/index.ts +5 -0
  104. package/src/electron/agent/llm/kimi-provider.ts +39 -0
  105. package/src/electron/agent/llm/openai-compatible-provider.ts +153 -0
  106. package/src/electron/agent/llm/openai-compatible.ts +133 -0
  107. package/src/electron/agent/llm/openai-oauth.ts +2 -1
  108. package/src/electron/agent/llm/openrouter-provider.ts +2 -1
  109. package/src/electron/agent/llm/provider-factory.ts +414 -6
  110. package/src/electron/agent/llm/types.ts +90 -1
  111. package/src/electron/agent/llm/xai-provider.ts +39 -0
  112. package/src/electron/agent/tools/box-tools.ts +239 -0
  113. package/src/electron/agent/tools/builtin-settings.ts +34 -0
  114. package/src/electron/agent/tools/dropbox-tools.ts +237 -0
  115. package/src/electron/agent/tools/google-drive-tools.ts +228 -0
  116. package/src/electron/agent/tools/notion-tools.ts +330 -0
  117. package/src/electron/agent/tools/onedrive-tools.ts +217 -0
  118. package/src/electron/agent/tools/registry.ts +565 -0
  119. package/src/electron/agent/tools/sharepoint-tools.ts +247 -0
  120. package/src/electron/agent/tools/shell-tools.ts +11 -3
  121. package/src/electron/agent/tools/x-tools.ts +1 -1
  122. package/src/electron/database/SecureSettingsRepository.ts +7 -1
  123. package/src/electron/gateway/index.ts +1 -0
  124. package/src/electron/gateway/router.ts +134 -149
  125. package/src/electron/ipc/canvas-handlers.ts +10 -0
  126. package/src/electron/ipc/handlers.ts +673 -153
  127. package/src/electron/main.ts +35 -0
  128. package/src/electron/mcp/oauth/connector-oauth.ts +448 -0
  129. package/src/electron/mcp/registry/MCPRegistryManager.ts +343 -12
  130. package/src/electron/memory/MemoryService.ts +5 -1
  131. package/src/electron/preload.ts +167 -4
  132. package/src/electron/settings/box-manager.ts +58 -0
  133. package/src/electron/settings/dropbox-manager.ts +58 -0
  134. package/src/electron/settings/google-drive-manager.ts +58 -0
  135. package/src/electron/settings/notion-manager.ts +60 -0
  136. package/src/electron/settings/onedrive-manager.ts +58 -0
  137. package/src/electron/settings/sharepoint-manager.ts +58 -0
  138. package/src/electron/utils/box-api.ts +184 -0
  139. package/src/electron/utils/dropbox-api.ts +171 -0
  140. package/src/electron/utils/env-migration.ts +22 -0
  141. package/src/electron/utils/google-drive-api.ts +183 -0
  142. package/src/electron/utils/notion-api.ts +126 -0
  143. package/src/electron/utils/onedrive-api.ts +137 -0
  144. package/src/electron/utils/sharepoint-api.ts +132 -0
  145. package/src/electron/utils/validation.ts +102 -1
  146. package/src/electron/utils/x-cli.ts +1 -1
  147. package/src/renderer/App.tsx +20 -2
  148. package/src/renderer/components/BoxSettings.tsx +203 -0
  149. package/src/renderer/components/BrowserView.tsx +101 -0
  150. package/src/renderer/components/BuiltinToolsSettings.tsx +105 -0
  151. package/src/renderer/components/CanvasPreview.tsx +68 -1
  152. package/src/renderer/components/ConnectorEnvModal.tsx +116 -0
  153. package/src/renderer/components/ConnectorSetupModal.tsx +566 -0
  154. package/src/renderer/components/ConnectorsSettings.tsx +397 -0
  155. package/src/renderer/components/DropboxSettings.tsx +202 -0
  156. package/src/renderer/components/GoogleDriveSettings.tsx +201 -0
  157. package/src/renderer/components/MCPSettings.tsx +56 -0
  158. package/src/renderer/components/MainContent.tsx +270 -34
  159. package/src/renderer/components/NotionSettings.tsx +231 -0
  160. package/src/renderer/components/Onboarding/Onboarding.tsx +13 -1
  161. package/src/renderer/components/OnboardingModal.tsx +70 -1
  162. package/src/renderer/components/OneDriveSettings.tsx +212 -0
  163. package/src/renderer/components/Settings.tsx +611 -8
  164. package/src/renderer/components/SharePointSettings.tsx +224 -0
  165. package/src/renderer/components/Sidebar.tsx +25 -9
  166. package/src/renderer/hooks/useOnboardingFlow.ts +21 -0
  167. package/src/renderer/styles/index.css +438 -25
  168. package/src/shared/channelMessages.ts +367 -4
  169. package/src/shared/llm-provider-catalog.ts +217 -0
  170. package/src/shared/types.ts +226 -1
@@ -0,0 +1,566 @@
1
+ import { useMemo, useState } from 'react';
2
+
3
+ export type ConnectorProvider = 'salesforce' | 'jira' | 'hubspot' | 'zendesk';
4
+
5
+ interface ConnectorSetupModalProps {
6
+ provider: ConnectorProvider;
7
+ serverId: string;
8
+ serverName: string;
9
+ initialEnv?: Record<string, string>;
10
+ onClose: () => void;
11
+ onSaved: () => void;
12
+ }
13
+
14
+ interface JiraResource {
15
+ id: string;
16
+ name: string;
17
+ url: string;
18
+ scopes?: string[];
19
+ }
20
+
21
+ export function ConnectorSetupModal({
22
+ provider,
23
+ serverId,
24
+ serverName,
25
+ initialEnv = {},
26
+ onClose,
27
+ onSaved,
28
+ }: ConnectorSetupModalProps) {
29
+ const [mode, setMode] = useState<'oauth' | 'manual'>('oauth');
30
+ const [saving, setSaving] = useState(false);
31
+ const [oauthBusy, setOauthBusy] = useState(false);
32
+ const [oauthError, setOauthError] = useState<string | null>(null);
33
+
34
+ // Salesforce fields
35
+ const [sfClientId, setSfClientId] = useState(initialEnv.SALESFORCE_CLIENT_ID || '');
36
+ const [sfClientSecret, setSfClientSecret] = useState(initialEnv.SALESFORCE_CLIENT_SECRET || '');
37
+ const [sfLoginUrl, setSfLoginUrl] = useState(initialEnv.SALESFORCE_LOGIN_URL || 'https://login.salesforce.com');
38
+ const [sfScopes, setSfScopes] = useState('api refresh_token');
39
+ const [sfInstanceUrl, setSfInstanceUrl] = useState(initialEnv.SALESFORCE_INSTANCE_URL || '');
40
+ const [sfAccessToken, setSfAccessToken] = useState(initialEnv.SALESFORCE_ACCESS_TOKEN || '');
41
+
42
+ // Jira fields
43
+ const [jiraClientId, setJiraClientId] = useState(initialEnv.JIRA_CLIENT_ID || '');
44
+ const [jiraClientSecret, setJiraClientSecret] = useState(initialEnv.JIRA_CLIENT_SECRET || '');
45
+ const [jiraScopes, setJiraScopes] = useState('read:jira-user read:jira-work write:jira-work offline_access');
46
+ const [jiraBaseUrl, setJiraBaseUrl] = useState(initialEnv.JIRA_BASE_URL || '');
47
+ const [jiraEmail, setJiraEmail] = useState(initialEnv.JIRA_EMAIL || '');
48
+ const [jiraApiToken, setJiraApiToken] = useState(initialEnv.JIRA_API_TOKEN || '');
49
+ const [jiraResources, setJiraResources] = useState<JiraResource[]>([]);
50
+ const [selectedJiraResourceId, setSelectedJiraResourceId] = useState('');
51
+ const [jiraOauthTokens, setJiraOauthTokens] = useState<{ accessToken: string; refreshToken?: string } | null>(null);
52
+
53
+ // HubSpot fields
54
+ const [hubspotClientId, setHubspotClientId] = useState(initialEnv.HUBSPOT_CLIENT_ID || '');
55
+ const [hubspotClientSecret, setHubspotClientSecret] = useState(initialEnv.HUBSPOT_CLIENT_SECRET || '');
56
+ const [hubspotScopes, setHubspotScopes] = useState(
57
+ 'crm.objects.contacts.read crm.objects.contacts.write crm.objects.companies.read crm.objects.companies.write crm.objects.deals.read crm.objects.deals.write'
58
+ );
59
+ const [hubspotAccessToken, setHubspotAccessToken] = useState(initialEnv.HUBSPOT_ACCESS_TOKEN || '');
60
+
61
+ // Zendesk fields
62
+ const [zendeskSubdomain, setZendeskSubdomain] = useState(initialEnv.ZENDESK_SUBDOMAIN || '');
63
+ const [zendeskClientId, setZendeskClientId] = useState(initialEnv.ZENDESK_CLIENT_ID || '');
64
+ const [zendeskClientSecret, setZendeskClientSecret] = useState(initialEnv.ZENDESK_CLIENT_SECRET || '');
65
+ const [zendeskScopes, setZendeskScopes] = useState('read write');
66
+ const [zendeskEmail, setZendeskEmail] = useState(initialEnv.ZENDESK_EMAIL || '');
67
+ const [zendeskApiToken, setZendeskApiToken] = useState(initialEnv.ZENDESK_API_TOKEN || '');
68
+ const [zendeskAccessToken, setZendeskAccessToken] = useState(initialEnv.ZENDESK_ACCESS_TOKEN || '');
69
+
70
+ const isSalesforce = provider === 'salesforce';
71
+ const isJira = provider === 'jira';
72
+ const isHubSpot = provider === 'hubspot';
73
+ const isZendesk = provider === 'zendesk';
74
+
75
+ const selectedJiraResource = useMemo(() => {
76
+ if (!selectedJiraResourceId) return null;
77
+ return jiraResources.find((resource) => resource.id === selectedJiraResourceId) || null;
78
+ }, [jiraResources, selectedJiraResourceId]);
79
+
80
+ const parseScopes = (value: string) =>
81
+ value
82
+ .split(/\s+/)
83
+ .map((s) => s.trim())
84
+ .filter(Boolean);
85
+
86
+ const sanitizeEnv = (env: Record<string, string | undefined>): Record<string, string> => {
87
+ const merged: Record<string, string> = { ...initialEnv };
88
+ Object.entries(env).forEach(([key, value]) => {
89
+ if (value === undefined || value === '') {
90
+ delete merged[key];
91
+ return;
92
+ }
93
+ merged[key] = value;
94
+ });
95
+ return merged;
96
+ };
97
+
98
+ const reconnectServer = async () => {
99
+ try {
100
+ await window.electronAPI.disconnectMCPServer(serverId);
101
+ } catch {
102
+ // ignore
103
+ }
104
+ await window.electronAPI.connectMCPServer(serverId);
105
+ };
106
+
107
+ const saveEnv = async (env: Record<string, string | undefined>) => {
108
+ setSaving(true);
109
+ try {
110
+ const merged = sanitizeEnv(env);
111
+ await window.electronAPI.updateMCPServer(serverId, { env: merged });
112
+ await reconnectServer();
113
+ onSaved();
114
+ onClose();
115
+ } catch (error: any) {
116
+ setOauthError(error.message || 'Failed to save credentials');
117
+ } finally {
118
+ setSaving(false);
119
+ }
120
+ };
121
+
122
+ const handleSalesforceOAuth = async () => {
123
+ setOauthBusy(true);
124
+ setOauthError(null);
125
+ try {
126
+ const result = await window.electronAPI.startConnectorOAuth({
127
+ provider: 'salesforce',
128
+ clientId: sfClientId,
129
+ clientSecret: sfClientSecret,
130
+ scopes: parseScopes(sfScopes),
131
+ loginUrl: sfLoginUrl,
132
+ });
133
+
134
+ await saveEnv({
135
+ SALESFORCE_ACCESS_TOKEN: result.accessToken,
136
+ SALESFORCE_REFRESH_TOKEN: result.refreshToken || '',
137
+ SALESFORCE_INSTANCE_URL: result.instanceUrl || sfInstanceUrl,
138
+ SALESFORCE_CLIENT_ID: sfClientId,
139
+ SALESFORCE_CLIENT_SECRET: sfClientSecret,
140
+ SALESFORCE_LOGIN_URL: sfLoginUrl,
141
+ });
142
+ } catch (error: any) {
143
+ setOauthError(error.message || 'Salesforce OAuth failed');
144
+ } finally {
145
+ setOauthBusy(false);
146
+ }
147
+ };
148
+
149
+ const handleJiraOAuth = async () => {
150
+ setOauthBusy(true);
151
+ setOauthError(null);
152
+ try {
153
+ const result = await window.electronAPI.startConnectorOAuth({
154
+ provider: 'jira',
155
+ clientId: jiraClientId,
156
+ clientSecret: jiraClientSecret,
157
+ scopes: parseScopes(jiraScopes),
158
+ });
159
+
160
+ const resources = result.resources || [];
161
+ setJiraResources(resources);
162
+ if (resources.length === 0) {
163
+ setOauthError('No Jira sites were returned for this account.');
164
+ return;
165
+ }
166
+ if (resources.length === 1) {
167
+ setSelectedJiraResourceId(resources[0].id);
168
+ }
169
+ setJiraOauthTokens({ accessToken: result.accessToken, refreshToken: result.refreshToken });
170
+ } catch (error: any) {
171
+ setOauthError(error.message || 'Jira OAuth failed');
172
+ } finally {
173
+ setOauthBusy(false);
174
+ }
175
+ };
176
+
177
+ const handleHubSpotOAuth = async () => {
178
+ setOauthBusy(true);
179
+ setOauthError(null);
180
+ try {
181
+ const result = await window.electronAPI.startConnectorOAuth({
182
+ provider: 'hubspot',
183
+ clientId: hubspotClientId,
184
+ clientSecret: hubspotClientSecret,
185
+ scopes: parseScopes(hubspotScopes),
186
+ });
187
+
188
+ await saveEnv({
189
+ HUBSPOT_ACCESS_TOKEN: result.accessToken,
190
+ HUBSPOT_REFRESH_TOKEN: result.refreshToken || '',
191
+ HUBSPOT_CLIENT_ID: hubspotClientId,
192
+ HUBSPOT_CLIENT_SECRET: hubspotClientSecret,
193
+ });
194
+ } catch (error: any) {
195
+ setOauthError(error.message || 'HubSpot OAuth failed');
196
+ } finally {
197
+ setOauthBusy(false);
198
+ }
199
+ };
200
+
201
+ const handleZendeskOAuth = async () => {
202
+ setOauthBusy(true);
203
+ setOauthError(null);
204
+ try {
205
+ const result = await window.electronAPI.startConnectorOAuth({
206
+ provider: 'zendesk',
207
+ clientId: zendeskClientId,
208
+ clientSecret: zendeskClientSecret,
209
+ scopes: parseScopes(zendeskScopes),
210
+ subdomain: zendeskSubdomain,
211
+ });
212
+
213
+ await saveEnv({
214
+ ZENDESK_SUBDOMAIN: zendeskSubdomain,
215
+ ZENDESK_ACCESS_TOKEN: result.accessToken,
216
+ ZENDESK_REFRESH_TOKEN: result.refreshToken || '',
217
+ ZENDESK_CLIENT_ID: zendeskClientId,
218
+ ZENDESK_CLIENT_SECRET: zendeskClientSecret,
219
+ ZENDESK_EMAIL: '',
220
+ ZENDESK_API_TOKEN: '',
221
+ });
222
+ } catch (error: any) {
223
+ setOauthError(error.message || 'Zendesk OAuth failed');
224
+ } finally {
225
+ setOauthBusy(false);
226
+ }
227
+ };
228
+
229
+ const handleManualSave = async () => {
230
+ if (isSalesforce) {
231
+ await saveEnv({
232
+ SALESFORCE_INSTANCE_URL: sfInstanceUrl,
233
+ SALESFORCE_ACCESS_TOKEN: sfAccessToken,
234
+ SALESFORCE_REFRESH_TOKEN: '',
235
+ });
236
+ } else if (isJira) {
237
+ await saveEnv({
238
+ JIRA_BASE_URL: jiraBaseUrl,
239
+ JIRA_EMAIL: jiraEmail,
240
+ JIRA_API_TOKEN: jiraApiToken,
241
+ JIRA_ACCESS_TOKEN: '',
242
+ JIRA_REFRESH_TOKEN: '',
243
+ JIRA_CLIENT_ID: '',
244
+ JIRA_CLIENT_SECRET: '',
245
+ });
246
+ } else if (isHubSpot) {
247
+ await saveEnv({
248
+ HUBSPOT_ACCESS_TOKEN: hubspotAccessToken,
249
+ HUBSPOT_REFRESH_TOKEN: '',
250
+ });
251
+ } else if (isZendesk) {
252
+ await saveEnv({
253
+ ZENDESK_SUBDOMAIN: zendeskSubdomain,
254
+ ZENDESK_EMAIL: zendeskEmail,
255
+ ZENDESK_API_TOKEN: zendeskApiToken,
256
+ ZENDESK_ACCESS_TOKEN: zendeskAccessToken,
257
+ ZENDESK_REFRESH_TOKEN: '',
258
+ ZENDESK_CLIENT_ID: '',
259
+ ZENDESK_CLIENT_SECRET: '',
260
+ });
261
+ }
262
+ };
263
+
264
+ const handleJiraOauthSave = async () => {
265
+ if (!jiraOauthTokens || !selectedJiraResource) {
266
+ setOauthError('Select a Jira site before saving.');
267
+ return;
268
+ }
269
+ const cloudBase = `https://api.atlassian.com/ex/jira/${selectedJiraResource.id}`;
270
+ await saveEnv({
271
+ JIRA_BASE_URL: cloudBase,
272
+ JIRA_ACCESS_TOKEN: jiraOauthTokens.accessToken,
273
+ JIRA_REFRESH_TOKEN: jiraOauthTokens.refreshToken || '',
274
+ JIRA_CLIENT_ID: jiraClientId,
275
+ JIRA_CLIENT_SECRET: jiraClientSecret,
276
+ JIRA_EMAIL: '',
277
+ JIRA_API_TOKEN: '',
278
+ });
279
+ };
280
+
281
+ return (
282
+ <div className="mcp-modal-overlay" onClick={onClose}>
283
+ <div className="mcp-modal connector-setup-modal" onClick={(e) => e.stopPropagation()}>
284
+ <div className="mcp-modal-header">
285
+ <div className="registry-details-title">
286
+ <h3>{serverName} Setup</h3>
287
+ </div>
288
+ <button className="mcp-modal-close" onClick={onClose}>
289
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
290
+ <path d="M18 6L6 18M6 6l12 12" />
291
+ </svg>
292
+ </button>
293
+ </div>
294
+ <div className="mcp-modal-content">
295
+ <div className="settings-field">
296
+ <label>Setup Method</label>
297
+ <div className="connector-mode-toggle">
298
+ <button
299
+ className={`button-small ${mode === 'oauth' ? 'button-primary' : 'button-secondary'}`}
300
+ onClick={() => setMode('oauth')}
301
+ >
302
+ OAuth
303
+ </button>
304
+ <button
305
+ className={`button-small ${mode === 'manual' ? 'button-primary' : 'button-secondary'}`}
306
+ onClick={() => setMode('manual')}
307
+ >
308
+ Manual Token
309
+ </button>
310
+ </div>
311
+ </div>
312
+
313
+ {mode === 'oauth' && isSalesforce && (
314
+ <>
315
+ <div className="settings-field">
316
+ <label>Client ID</label>
317
+ <input className="settings-input" value={sfClientId} onChange={(e) => setSfClientId(e.target.value)} />
318
+ </div>
319
+ <div className="settings-field">
320
+ <label>Client Secret</label>
321
+ <input className="settings-input" type="password" value={sfClientSecret} onChange={(e) => setSfClientSecret(e.target.value)} />
322
+ </div>
323
+ <div className="settings-field">
324
+ <label>Login URL</label>
325
+ <input className="settings-input" value={sfLoginUrl} onChange={(e) => setSfLoginUrl(e.target.value)} />
326
+ <p className="settings-hint">Use https://test.salesforce.com for sandbox orgs.</p>
327
+ </div>
328
+ <div className="settings-field">
329
+ <label>Scopes</label>
330
+ <input className="settings-input" value={sfScopes} onChange={(e) => setSfScopes(e.target.value)} />
331
+ </div>
332
+ <p className="settings-hint">Redirect URI: http://127.0.0.1:18765/oauth/callback</p>
333
+ <div className="connector-setup-actions">
334
+ <button
335
+ className="button-primary"
336
+ onClick={handleSalesforceOAuth}
337
+ disabled={oauthBusy || !sfClientId || !sfClientSecret}
338
+ >
339
+ {oauthBusy ? 'Authorizing...' : 'Authorize Salesforce'}
340
+ </button>
341
+ </div>
342
+ </>
343
+ )}
344
+
345
+ {mode === 'oauth' && isJira && (
346
+ <>
347
+ <div className="settings-field">
348
+ <label>Client ID</label>
349
+ <input className="settings-input" value={jiraClientId} onChange={(e) => setJiraClientId(e.target.value)} />
350
+ </div>
351
+ <div className="settings-field">
352
+ <label>Client Secret</label>
353
+ <input className="settings-input" type="password" value={jiraClientSecret} onChange={(e) => setJiraClientSecret(e.target.value)} />
354
+ </div>
355
+ <div className="settings-field">
356
+ <label>Scopes</label>
357
+ <input className="settings-input" value={jiraScopes} onChange={(e) => setJiraScopes(e.target.value)} />
358
+ </div>
359
+ <p className="settings-hint">Redirect URI: http://127.0.0.1:18765/oauth/callback</p>
360
+ <div className="connector-setup-actions">
361
+ <button
362
+ className="button-primary"
363
+ onClick={handleJiraOAuth}
364
+ disabled={oauthBusy || !jiraClientId || !jiraClientSecret}
365
+ >
366
+ {oauthBusy ? 'Authorizing...' : 'Authorize Jira'}
367
+ </button>
368
+ </div>
369
+
370
+ {jiraResources.length > 0 && (
371
+ <div className="settings-field">
372
+ <label>Select Jira Site</label>
373
+ <select
374
+ className="settings-input"
375
+ value={selectedJiraResourceId}
376
+ onChange={(e) => setSelectedJiraResourceId(e.target.value)}
377
+ >
378
+ <option value="">Choose a site</option>
379
+ {jiraResources.map((resource) => (
380
+ <option key={resource.id} value={resource.id}>
381
+ {resource.name} ({resource.url})
382
+ </option>
383
+ ))}
384
+ </select>
385
+ </div>
386
+ )}
387
+
388
+ {jiraOauthTokens && (
389
+ <div className="connector-setup-actions">
390
+ <button
391
+ className="button-primary"
392
+ onClick={handleJiraOauthSave}
393
+ disabled={!selectedJiraResourceId || saving}
394
+ >
395
+ {saving ? 'Saving...' : 'Save Jira Connection'}
396
+ </button>
397
+ </div>
398
+ )}
399
+ </>
400
+ )}
401
+
402
+ {mode === 'oauth' && isHubSpot && (
403
+ <>
404
+ <div className="settings-field">
405
+ <label>Client ID</label>
406
+ <input className="settings-input" value={hubspotClientId} onChange={(e) => setHubspotClientId(e.target.value)} />
407
+ </div>
408
+ <div className="settings-field">
409
+ <label>Client Secret</label>
410
+ <input className="settings-input" type="password" value={hubspotClientSecret} onChange={(e) => setHubspotClientSecret(e.target.value)} />
411
+ </div>
412
+ <div className="settings-field">
413
+ <label>Scopes</label>
414
+ <input className="settings-input" value={hubspotScopes} onChange={(e) => setHubspotScopes(e.target.value)} />
415
+ </div>
416
+ <p className="settings-hint">Redirect URI: http://127.0.0.1:18765/oauth/callback</p>
417
+ <div className="connector-setup-actions">
418
+ <button
419
+ className="button-primary"
420
+ onClick={handleHubSpotOAuth}
421
+ disabled={oauthBusy || !hubspotClientId || !hubspotClientSecret}
422
+ >
423
+ {oauthBusy ? 'Authorizing...' : 'Authorize HubSpot'}
424
+ </button>
425
+ </div>
426
+ </>
427
+ )}
428
+
429
+ {mode === 'oauth' && isZendesk && (
430
+ <>
431
+ <div className="settings-field">
432
+ <label>Subdomain</label>
433
+ <input className="settings-input" value={zendeskSubdomain} onChange={(e) => setZendeskSubdomain(e.target.value)} placeholder="your-company" />
434
+ </div>
435
+ <div className="settings-field">
436
+ <label>Client ID</label>
437
+ <input className="settings-input" value={zendeskClientId} onChange={(e) => setZendeskClientId(e.target.value)} />
438
+ </div>
439
+ <div className="settings-field">
440
+ <label>Client Secret</label>
441
+ <input className="settings-input" type="password" value={zendeskClientSecret} onChange={(e) => setZendeskClientSecret(e.target.value)} />
442
+ </div>
443
+ <div className="settings-field">
444
+ <label>Scopes</label>
445
+ <input className="settings-input" value={zendeskScopes} onChange={(e) => setZendeskScopes(e.target.value)} />
446
+ </div>
447
+ <p className="settings-hint">Redirect URI: http://127.0.0.1:18765/oauth/callback</p>
448
+ <div className="connector-setup-actions">
449
+ <button
450
+ className="button-primary"
451
+ onClick={handleZendeskOAuth}
452
+ disabled={oauthBusy || !zendeskClientId || !zendeskClientSecret || !zendeskSubdomain}
453
+ >
454
+ {oauthBusy ? 'Authorizing...' : 'Authorize Zendesk'}
455
+ </button>
456
+ </div>
457
+ </>
458
+ )}
459
+
460
+ {mode === 'manual' && isSalesforce && (
461
+ <>
462
+ <div className="settings-field">
463
+ <label>Instance URL</label>
464
+ <input className="settings-input" value={sfInstanceUrl} onChange={(e) => setSfInstanceUrl(e.target.value)} />
465
+ </div>
466
+ <div className="settings-field">
467
+ <label>Access Token</label>
468
+ <textarea className="settings-textarea" rows={3} value={sfAccessToken} onChange={(e) => setSfAccessToken(e.target.value)} />
469
+ </div>
470
+ <div className="connector-setup-actions">
471
+ <button
472
+ className="button-primary"
473
+ onClick={handleManualSave}
474
+ disabled={!sfInstanceUrl || !sfAccessToken || saving}
475
+ >
476
+ {saving ? 'Saving...' : 'Save Salesforce Credentials'}
477
+ </button>
478
+ </div>
479
+ </>
480
+ )}
481
+
482
+ {mode === 'manual' && isJira && (
483
+ <>
484
+ <div className="settings-field">
485
+ <label>Base URL</label>
486
+ <input className="settings-input" value={jiraBaseUrl} onChange={(e) => setJiraBaseUrl(e.target.value)} />
487
+ </div>
488
+ <div className="settings-field">
489
+ <label>Email</label>
490
+ <input className="settings-input" value={jiraEmail} onChange={(e) => setJiraEmail(e.target.value)} />
491
+ </div>
492
+ <div className="settings-field">
493
+ <label>API Token</label>
494
+ <textarea className="settings-textarea" rows={3} value={jiraApiToken} onChange={(e) => setJiraApiToken(e.target.value)} />
495
+ </div>
496
+ <div className="connector-setup-actions">
497
+ <button
498
+ className="button-primary"
499
+ onClick={handleManualSave}
500
+ disabled={!jiraBaseUrl || !jiraEmail || !jiraApiToken || saving}
501
+ >
502
+ {saving ? 'Saving...' : 'Save Jira Credentials'}
503
+ </button>
504
+ </div>
505
+ </>
506
+ )}
507
+
508
+ {mode === 'manual' && isHubSpot && (
509
+ <>
510
+ <div className="settings-field">
511
+ <label>Access Token</label>
512
+ <textarea className="settings-textarea" rows={3} value={hubspotAccessToken} onChange={(e) => setHubspotAccessToken(e.target.value)} />
513
+ </div>
514
+ <div className="connector-setup-actions">
515
+ <button
516
+ className="button-primary"
517
+ onClick={handleManualSave}
518
+ disabled={!hubspotAccessToken || saving}
519
+ >
520
+ {saving ? 'Saving...' : 'Save HubSpot Credentials'}
521
+ </button>
522
+ </div>
523
+ </>
524
+ )}
525
+
526
+ {mode === 'manual' && isZendesk && (
527
+ <>
528
+ <div className="settings-field">
529
+ <label>Subdomain</label>
530
+ <input className="settings-input" value={zendeskSubdomain} onChange={(e) => setZendeskSubdomain(e.target.value)} placeholder="your-company" />
531
+ </div>
532
+ <div className="settings-field">
533
+ <label>Email</label>
534
+ <input className="settings-input" value={zendeskEmail} onChange={(e) => setZendeskEmail(e.target.value)} />
535
+ </div>
536
+ <div className="settings-field">
537
+ <label>API Token</label>
538
+ <textarea className="settings-textarea" rows={3} value={zendeskApiToken} onChange={(e) => setZendeskApiToken(e.target.value)} />
539
+ </div>
540
+ <div className="settings-field">
541
+ <label>Access Token (optional)</label>
542
+ <textarea className="settings-textarea" rows={3} value={zendeskAccessToken} onChange={(e) => setZendeskAccessToken(e.target.value)} />
543
+ </div>
544
+ <div className="connector-setup-actions">
545
+ <button
546
+ className="button-primary"
547
+ onClick={handleManualSave}
548
+ disabled={!zendeskSubdomain || !zendeskEmail || !zendeskApiToken || saving}
549
+ >
550
+ {saving ? 'Saving...' : 'Save Zendesk Credentials'}
551
+ </button>
552
+ </div>
553
+ </>
554
+ )}
555
+
556
+ {oauthError && (
557
+ <div className="mcp-server-error">
558
+ <span className="mcp-error-icon">⚠</span>
559
+ {oauthError}
560
+ </div>
561
+ )}
562
+ </div>
563
+ </div>
564
+ </div>
565
+ );
566
+ }