@grackle-ai/mcp 0.131.0 → 0.132.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -99
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +74 -20
- package/dist/mcp-server.js.map +1 -1
- package/dist/scope-enforcement.d.ts.map +1 -1
- package/dist/scope-enforcement.js.map +1 -1
- package/dist/standalone.js +1 -1
- package/dist/standalone.js.map +1 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/tool-registry.js.map +1 -1
- package/dist/tools/component.d.ts.map +1 -1
- package/dist/tools/component.js +128 -27
- package/dist/tools/component.js.map +1 -1
- package/dist/tools/credential.d.ts.map +1 -1
- package/dist/tools/credential.js +7 -3
- package/dist/tools/credential.js.map +1 -1
- package/dist/tools/env.d.ts.map +1 -1
- package/dist/tools/env.js +8 -18
- package/dist/tools/env.js.map +1 -1
- package/dist/tools/escalation.d.ts.map +1 -1
- package/dist/tools/escalation.js +15 -4
- package/dist/tools/escalation.js.map +1 -1
- package/dist/tools/ipc.d.ts.map +1 -1
- package/dist/tools/ipc.js +109 -23
- package/dist/tools/ipc.js.map +1 -1
- package/dist/tools/logs.d.ts.map +1 -1
- package/dist/tools/logs.js +23 -5
- package/dist/tools/logs.js.map +1 -1
- package/dist/tools/persona.d.ts.map +1 -1
- package/dist/tools/persona.js +8 -2
- package/dist/tools/persona.js.map +1 -1
- package/dist/tools/schedule.d.ts.map +1 -1
- package/dist/tools/schedule.js +7 -2
- package/dist/tools/schedule.js.map +1 -1
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +42 -10
- package/dist/tools/session.js.map +1 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +52 -32
- package/dist/tools/task.js.map +1 -1
- package/dist/tools/token.d.ts.map +1 -1
- package/dist/tools/token.js +20 -6
- package/dist/tools/token.js.map +1 -1
- package/dist/tools/usage.d.ts.map +1 -1
- package/dist/tools/usage.js +9 -3
- package/dist/tools/usage.js.map +1 -1
- package/dist/tools/workpad.d.ts.map +1 -1
- package/dist/tools/workpad.js +38 -8
- package/dist/tools/workpad.js.map +1 -1
- package/dist/tools/workspace.d.ts.map +1 -1
- package/dist/tools/workspace.js +7 -27
- package/dist/tools/workspace.js.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -34,13 +34,13 @@ The MCP server listens on `http://127.0.0.1:7435/mcp` by default.
|
|
|
34
34
|
|
|
35
35
|
All configuration is via environment variables:
|
|
36
36
|
|
|
37
|
-
| Variable
|
|
38
|
-
|
|
39
|
-
| `GRACKLE_MCP_PORT` | `7435`
|
|
40
|
-
| `GRACKLE_HOST`
|
|
41
|
-
| `GRACKLE_URL`
|
|
42
|
-
| `GRACKLE_API_KEY`
|
|
43
|
-
| `LOG_LEVEL`
|
|
37
|
+
| Variable | Default | Description |
|
|
38
|
+
| ------------------ | ----------------------- | -------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `GRACKLE_MCP_PORT` | `7435` | Port the MCP server listens on |
|
|
40
|
+
| `GRACKLE_HOST` | `127.0.0.1` | Bind address (must be a loopback address) |
|
|
41
|
+
| `GRACKLE_URL` | `http://127.0.0.1:7434` | URL of the Grackle gRPC server to connect to |
|
|
42
|
+
| `GRACKLE_API_KEY` | _(auto-loaded)_ | API key for authenticating with the gRPC server. If not set, reads from `~/.grackle/api-key` |
|
|
43
|
+
| `LOG_LEVEL` | `info` | Logging level (`debug`, `info`, `warn`, `error`) |
|
|
44
44
|
|
|
45
45
|
## Programmatic Usage
|
|
46
46
|
|
|
@@ -102,16 +102,16 @@ The MCP server exposes a rich set of tools organized into groups. Each tool vali
|
|
|
102
102
|
|
|
103
103
|
Manage compute environments where agents run (Docker, SSH, Codespace, local).
|
|
104
104
|
|
|
105
|
-
| Tool
|
|
106
|
-
|
|
107
|
-
| `env_list`
|
|
108
|
-
| `env_list_docker_containers` | List running Docker containers that can be attached to (Docker adapter attach mode).
|
|
109
|
-
| `env_add`
|
|
110
|
-
| `env_provision`
|
|
111
|
-
| `env_stop`
|
|
112
|
-
| `env_destroy`
|
|
113
|
-
| `env_remove`
|
|
114
|
-
| `env_wake`
|
|
105
|
+
| Tool | Description | Parameters |
|
|
106
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
107
|
+
| `env_list` | List all registered environments with status, adapter type, and runtime. | _(none)_ |
|
|
108
|
+
| `env_list_docker_containers` | List running Docker containers that can be attached to (Docker adapter attach mode). | _(none)_ |
|
|
109
|
+
| `env_add` | Register a new environment. The `adapterConfig` fields depend on `adapterType` (see below). For the Docker adapter, set `adapterConfig.attach` to an existing container name/ID to attach instead of creating one. | `displayName` (string), `adapterType` (`local` \| `ssh` \| `codespace` \| `docker`), `adapterConfig?` (object), `githubAccountId?` (string, codespace/docker) |
|
|
110
|
+
| `env_provision` | Provision an environment — start resources, install the agent, and connect. | `environmentId` (string), `force?` (boolean) |
|
|
111
|
+
| `env_stop` | Stop a running environment without destroying its resources. | `environmentId` (string) |
|
|
112
|
+
| `env_destroy` | Destroy an environment's backing resources (e.g., delete the container). | `environmentId` (string) |
|
|
113
|
+
| `env_remove` | Remove an environment registration. Must be stopped first. | `environmentId` (string) |
|
|
114
|
+
| `env_wake` | Wake a stopped environment by re-provisioning it. | `environmentId` (string) |
|
|
115
115
|
|
|
116
116
|
`env_add` is a discriminated union on `adapterType` — the tool's input schema advertises the exact `adapterConfig` fields valid for each adapter, and unknown fields are rejected. Pick the adapter that matches how the environment is reached:
|
|
117
117
|
|
|
@@ -124,125 +124,125 @@ Manage compute environments where agents run (Docker, SSH, Codespace, local).
|
|
|
124
124
|
|
|
125
125
|
Manage AI agent sessions — spawn, monitor, interact, and terminate.
|
|
126
126
|
|
|
127
|
-
| Tool
|
|
128
|
-
|
|
129
|
-
| `session_spawn`
|
|
130
|
-
| `session_resume`
|
|
131
|
-
| `session_status`
|
|
132
|
-
| `session_kill`
|
|
133
|
-
| `session_attach`
|
|
134
|
-
| `session_send_input` | Send a text message to a session waiting for user input.
|
|
127
|
+
| Tool | Description | Parameters |
|
|
128
|
+
| -------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
129
|
+
| `session_spawn` | Spawn a new agent session with a prompt and optional model config. | `environmentId` (string), `prompt` (string), `maxTurns?` (int), `personaId?` (string), `workingDirectory?` (string) |
|
|
130
|
+
| `session_resume` | Resume a stopped agent session. | `sessionId` (string) |
|
|
131
|
+
| `session_status` | List sessions with optional filtering by environment and status. | `environmentId?` (string), `all?` (boolean, default false) |
|
|
132
|
+
| `session_kill` | Terminate a running session. Hard kill by default; graceful=true sends SIGTERM. | `sessionId` (string), `graceful?` (boolean, default false) |
|
|
133
|
+
| `session_attach` | Stream events from a running session for a limited duration. | `sessionId` (string), `timeoutSeconds?` (int, default 30, max 300), `maxEvents?` (int) |
|
|
134
|
+
| `session_send_input` | Send a text message to a session waiting for user input. | `sessionId` (string), `text` (string) |
|
|
135
135
|
|
|
136
136
|
### Workspace Tools
|
|
137
137
|
|
|
138
138
|
Manage workspaces that group tasks, agents, and repositories.
|
|
139
139
|
|
|
140
|
-
| Tool
|
|
141
|
-
|
|
142
|
-
| `workspace_list`
|
|
143
|
-
| `workspace_create`
|
|
144
|
-
| `workspace_get`
|
|
145
|
-
| `workspace_update`
|
|
146
|
-
| `workspace_archive`
|
|
147
|
-
| `workspace_link_environment`
|
|
148
|
-
| `workspace_unlink_environment` | Remove a linked environment from a workspace's pool.
|
|
140
|
+
| Tool | Description | Parameters |
|
|
141
|
+
| ------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
142
|
+
| `workspace_list` | List all workspaces with names, descriptions, repos, and status. | `environmentId?` (string) |
|
|
143
|
+
| `workspace_create` | Create a new workspace. | `name` (string), `environmentId` (string), `description?` (string), `repoUrl?` (string), `workingDirectory?` (string), `useWorktrees?` (boolean), `defaultPersonaId?` (string) |
|
|
144
|
+
| `workspace_get` | Get full details of a workspace by ID. | `workspaceId` (string) |
|
|
145
|
+
| `workspace_update` | Update a workspace's name, description, repo, or settings. | `workspaceId` (string), `name?`, `description?`, `repoUrl?`, `environmentId?`, `workingDirectory?`, `useWorktrees?`, `defaultPersonaId?` |
|
|
146
|
+
| `workspace_archive` | Archive a workspace, marking it as inactive. | `workspaceId` (string) |
|
|
147
|
+
| `workspace_link_environment` | Link an additional environment to a workspace's pool. | `workspaceId` (string), `environmentId` (string) |
|
|
148
|
+
| `workspace_unlink_environment` | Remove a linked environment from a workspace's pool. | `workspaceId` (string), `environmentId` (string) |
|
|
149
149
|
|
|
150
150
|
### Task Tools
|
|
151
151
|
|
|
152
152
|
Create, manage, and run tasks within workspaces. Supports hierarchical task trees and dependency gating.
|
|
153
153
|
|
|
154
|
-
| Tool
|
|
155
|
-
|
|
156
|
-
| `task_list`
|
|
157
|
-
| `task_search`
|
|
158
|
-
| `task_create`
|
|
159
|
-
| `task_show`
|
|
160
|
-
| `task_update`
|
|
161
|
-
| `task_start`
|
|
162
|
-
| `task_delete`
|
|
163
|
-
| `task_complete` | Mark a task as complete (sticky status).
|
|
164
|
-
| `task_resume`
|
|
154
|
+
| Tool | Description | Parameters |
|
|
155
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
156
|
+
| `task_list` | List tasks with optional search and status filters. | `workspaceId?` (string), `search?` (string), `status?` (string: `not_started`, `working`, `paused`, `complete`, `failed`) |
|
|
157
|
+
| `task_search` | Fuzzy search tasks by title or description, ranked by relevance. Returns results with a `relevanceScore` (0.0–1.0, higher = better match). Prefer this over `task_list` when matching approximate descriptions. | `query` (string), `workspaceId?` (string), `limit?` (number, default 10), `status?` (string) |
|
|
158
|
+
| `task_create` | Create a new task with dependencies and parent hierarchy. | `workspaceId?` (string), `title` (string), `description?` (string), `dependsOn?` (string[]), `parentTaskId?` (string), `canDecompose?` (boolean), `defaultPersonaId?` (string) |
|
|
159
|
+
| `task_show` | Get full details of a task. | `taskId` (string) |
|
|
160
|
+
| `task_update` | Update a task's title, description, status, or dependencies. | `taskId` (string), `title?`, `description?`, `status?` (enum), `dependsOn?` (string[]), `sessionId?` (string) |
|
|
161
|
+
| `task_start` | Start a task by spawning an agent session. Supports IPC pipe modes. | `taskId` (string), `personaId?` (string), `environmentId?` (string), `notes?` (string), `pipe?` (`sync` \| `async` \| `detach`) |
|
|
162
|
+
| `task_delete` | Permanently delete a task. | `taskId` (string) |
|
|
163
|
+
| `task_complete` | Mark a task as complete (sticky status). | `taskId` (string) |
|
|
164
|
+
| `task_resume` | Resume the latest session for a task. | `taskId` (string) |
|
|
165
165
|
|
|
166
166
|
### Persona Tools
|
|
167
167
|
|
|
168
168
|
Manage agent personas — reusable templates defining system prompt, runtime, and model.
|
|
169
169
|
|
|
170
|
-
| Tool
|
|
171
|
-
|
|
172
|
-
| `persona_list`
|
|
170
|
+
| Tool | Description | Parameters |
|
|
171
|
+
| ---------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| `persona_list` | List all available personas. | _(none)_ |
|
|
173
173
|
| `persona_create` | Create a new persona template (`agent` or `script` type). | `name` (string), `systemPrompt?` (string), `description?` (string), `runtime?` (string), `model?` (string), `maxTurns?` (int), `type?` (`agent` \| `script`), `script?` (string) |
|
|
174
|
-
| `persona_show`
|
|
175
|
-
| `persona_edit`
|
|
176
|
-
| `persona_delete` | Delete a persona permanently.
|
|
174
|
+
| `persona_show` | Get full details of a persona. | `personaId` (string) |
|
|
175
|
+
| `persona_edit` | Update an existing persona. | `personaId` (string), `name?`, `systemPrompt?`, `description?`, `runtime?`, `model?`, `maxTurns?`, `type?`, `script?` |
|
|
176
|
+
| `persona_delete` | Delete a persona permanently. | `personaId` (string) |
|
|
177
177
|
|
|
178
178
|
### Knowledge Graph Tools
|
|
179
179
|
|
|
180
180
|
Search a semantic knowledge graph across sessions and task context.
|
|
181
181
|
|
|
182
|
-
| Tool
|
|
183
|
-
|
|
184
|
-
| `knowledge_search`
|
|
185
|
-
| `knowledge_get_node`
|
|
186
|
-
| `knowledge_create_node` | Create a new knowledge entry (decision, insight, concept, snippet).
|
|
182
|
+
| Tool | Description | Parameters |
|
|
183
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
184
|
+
| `knowledge_search` | Find how related/prior work in the workspace was approached. Semantic search over the knowledge graph (tasks, sessions, transcript chunks). | `query` (string), `limit?` (int, max 50), `workspaceId?` (string), `expand?` (boolean), `expandDepth?` (int, max 5) |
|
|
185
|
+
| `knowledge_get_node` | Retrieve a specific node by ID with optional neighbor expansion. | `id` (string), `expand?` (boolean), `expandDepth?` (int, max 5) |
|
|
186
|
+
| `knowledge_create_node` | Create a new knowledge entry (decision, insight, concept, snippet). | `title` (string), `content` (string), `category?` (string), `tags?` (string[]), `workspaceId?` (string), `edges?` (array of `{toId, type}`) |
|
|
187
187
|
|
|
188
188
|
### IPC Tools
|
|
189
189
|
|
|
190
190
|
Inter-process communication between parent and child agent sessions.
|
|
191
191
|
|
|
192
|
-
| Tool
|
|
193
|
-
|
|
194
|
-
| `ipc_spawn`
|
|
195
|
-
| `ipc_write`
|
|
196
|
-
| `ipc_close`
|
|
197
|
-
| `ipc_terminate`
|
|
198
|
-
| `ipc_list_fds`
|
|
199
|
-
| `ipc_list_streams`
|
|
200
|
-
| `ipc_create_stream` | Create a named stream for inter-session communication. Returns an rw fd.
|
|
201
|
-
| `ipc_attach`
|
|
202
|
-
| `ipc_share_stream`
|
|
192
|
+
| Tool | Description | Parameters |
|
|
193
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
194
|
+
| `ipc_spawn` | Spawn a child agent session with an IPC pipe. | `prompt` (string), `pipe` (`sync` \| `async` \| `detach`), `environmentId` (string), `personaId?` (string), `maxTurns?` (int) |
|
|
195
|
+
| `ipc_write` | Write a message to a child session via a file descriptor. | `fd` (int), `message` (string) |
|
|
196
|
+
| `ipc_close` | Close a file descriptor, optionally stopping the child. | `fd` (int) |
|
|
197
|
+
| `ipc_terminate` | Send SIGTERM to a child session via its fd for graceful shutdown. | `fd` (int) |
|
|
198
|
+
| `ipc_list_fds` | List your open file descriptors (IPC connections). | _(none)_ |
|
|
199
|
+
| `ipc_list_streams` | List all active IPC streams with subscriber details and buffer depth. | _(none)_ |
|
|
200
|
+
| `ipc_create_stream` | Create a named stream for inter-session communication. Returns an rw fd. | `name` (string), `selfEcho?` (boolean, default `false`) |
|
|
201
|
+
| `ipc_attach` | Grant another session access to a stream you hold an fd on. | `fd` (int), `targetSessionId` (string), `permission?` (`r` \| `w` \| `rw`), `deliveryMode?` (`sync` \| `async` \| `detach`) |
|
|
202
|
+
| `ipc_share_stream` | Share a stream with your parent session. Auto-discovers the parent via the inherited pipe fd, grants access, and sends a `[stream-ref]` notification through the pipe. | `fd?` (int) or `streamName?` (string; exactly one required), `permission?` (`r` \| `w` \| `rw`), `deliveryMode?` (`sync` \| `async` \| `detach`) |
|
|
203
203
|
|
|
204
204
|
### Log Tools
|
|
205
205
|
|
|
206
206
|
Retrieve session logs — raw events, formatted transcripts, or live tails.
|
|
207
207
|
|
|
208
|
-
| Tool
|
|
209
|
-
|
|
208
|
+
| Tool | Description | Parameters |
|
|
209
|
+
| ---------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
210
210
|
| `logs_get` | Retrieve session logs in raw, transcript, or live-tail mode. | `sessionId` (string), `transcript?` (boolean), `tail?` (boolean), `timeoutSeconds?` (int, default 10, max 60), `maxEvents?` (int) |
|
|
211
211
|
|
|
212
212
|
### Token Tools
|
|
213
213
|
|
|
214
214
|
Manage secrets that are auto-forwarded to environments.
|
|
215
215
|
|
|
216
|
-
| Tool
|
|
217
|
-
|
|
218
|
-
| `token_list`
|
|
219
|
-
| `token_set`
|
|
220
|
-
| `token_delete` | Delete a configured token.
|
|
216
|
+
| Tool | Description | Parameters |
|
|
217
|
+
| -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
218
|
+
| `token_list` | List configured tokens (values are never returned). | _(none)_ |
|
|
219
|
+
| `token_set` | Set a token for auto-forwarding to environments. | `name` (string), `value` (string), `type?` (`env_var` \| `file`), `envVar?` (string), `filePath?` (string) |
|
|
220
|
+
| `token_delete` | Delete a configured token. | `name` (string) |
|
|
221
221
|
|
|
222
222
|
### Credential Provider Tools
|
|
223
223
|
|
|
224
224
|
Configure which credential providers (Claude, GitHub, Copilot, Codex) are auto-forwarded.
|
|
225
225
|
|
|
226
|
-
| Tool
|
|
227
|
-
|
|
228
|
-
| `credential_provider_list` | List current provider configuration. |
|
|
229
|
-
| `credential_provider_set`
|
|
226
|
+
| Tool | Description | Parameters |
|
|
227
|
+
| -------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
228
|
+
| `credential_provider_list` | List current provider configuration. | _(none)_ |
|
|
229
|
+
| `credential_provider_set` | Set a provider mode. | `provider` (`claude` \| `github` \| `copilot` \| `codex`), `value` (`off` \| `on` \| `subscription` \| `api_key`) |
|
|
230
230
|
|
|
231
231
|
### Config Tools
|
|
232
232
|
|
|
233
233
|
Read and write global configuration settings.
|
|
234
234
|
|
|
235
|
-
| Tool
|
|
236
|
-
|
|
237
|
-
| `config_get_default_persona` | Get the default persona for new sessions. |
|
|
235
|
+
| Tool | Description | Parameters |
|
|
236
|
+
| ---------------------------- | ----------------------------------------- | -------------------- |
|
|
237
|
+
| `config_get_default_persona` | Get the default persona for new sessions. | _(none)_ |
|
|
238
238
|
| `config_set_default_persona` | Set the default persona for new sessions. | `personaId` (string) |
|
|
239
239
|
|
|
240
240
|
### Usage Tools
|
|
241
241
|
|
|
242
242
|
Query aggregated token usage and cost data.
|
|
243
243
|
|
|
244
|
-
| Tool
|
|
245
|
-
|
|
244
|
+
| Tool | Description | Parameters |
|
|
245
|
+
| ----------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
246
246
|
| `usage_get` | Get token usage and cost for a session, task, task tree, workspace, or environment. | `scope` (`session` \| `task` \| `task_tree` \| `workspace` \| `environment`), `id` (string) |
|
|
247
247
|
|
|
248
248
|
### Component & Widget Tools
|
|
@@ -251,17 +251,17 @@ Query aggregated token usage and cost data.
|
|
|
251
251
|
|
|
252
252
|
`show_hello_widget` is the Grackle-served demo: it references a static `ui://` resource and appears in `tools/list` **only** when the host advertises the `io.modelcontextprotocol/ui` extension. The **component registry** tools (#1269) let an agent author and reuse components at runtime; they are ordinary scoped tools (always listed) and the rendered output is captured by the broker into the session's chat.
|
|
253
253
|
|
|
254
|
-
| Tool
|
|
255
|
-
|
|
256
|
-
| `show_hello_widget`
|
|
257
|
-
| `component_register` | Register a reusable component in the workspace. `source` is JSX (`grackle-react`, default) or HTML (`mcp-app-html`). Returns the component id.
|
|
258
|
-
| `component_update`
|
|
259
|
-
| `component_list`
|
|
260
|
-
| `component_search`
|
|
261
|
-
| `component_promote`
|
|
262
|
-
| `component_render`
|
|
263
|
-
| `component_show`
|
|
264
|
-
| `widget_show`
|
|
254
|
+
| Tool | Description | Parameters |
|
|
255
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `show_hello_widget` | Display the Grackle hello widget — a minimal interactive MCP Apps UI that echoes the provided message. | `message` (string, optional) |
|
|
257
|
+
| `component_register` | Register a reusable component in the workspace. `source` is JSX (`grackle-react`, default) or HTML (`mcp-app-html`). Returns the component id. | `name` (string), `source` (string), `rendererKind` (optional), `description` (optional), `propsSchema` (JSON Schema string, optional) |
|
|
258
|
+
| `component_update` | Update a registered component's source/name/description/props schema; bumps its version. | `id` (string), `source`/`name`/`description`/`propsSchema` (optional) |
|
|
259
|
+
| `component_list` | List the reusable components registered in the workspace. | — |
|
|
260
|
+
| `component_search` | Keyword-search registered components **and** Grackle's built-in components by name/description. Find a component to reuse before authoring. `builtin:true` results are composed in JSX; others are rendered via `component_render`. | `query` (string), `limit` (optional), `workspaceId` (auto) |
|
|
261
|
+
| `component_promote` | Promote a registered component to its own `render_<name>` MCP tool (or demote with `promoted:false`). Resolve by `id` or `name`. | `id` (optional), `name` (optional), `promoted` (optional, default `true`) |
|
|
262
|
+
| `component_render` | Render a registered component inline (by `id` or `name`), optionally passing `props`. Props are validated against the component's `propsSchema`. | `id` (optional), `name` (optional), `props` (object, optional) |
|
|
263
|
+
| `component_show` | Render a one-off React/JSX component inline against the Grackle component library (no persistence). `source` calls `render(<Component {...props}/>)`; `React`, `props`, and Grackle components are in scope. | `source` (string), `props` (object, optional) |
|
|
264
|
+
| `widget_show` | Render a one-off raw-HTML body inline, without persisting it. | `body` (string), `props` (object, optional) |
|
|
265
265
|
|
|
266
266
|
Components are **workspace-scoped**: a session may only register/render components in its own workspace (the `workspaceId` is taken from the session's scoped token). A component's `propsSchema` (a JSON Schema) is validated at register, and render-time `props` are validated against it (via zod's `fromJSONSchema`). Renderers run in the cross-origin sandbox: `mcp-app-html` allows inline scripts (`script-src 'unsafe-inline'`); `grackle-react` runs the React runtime that transpiles + evaluates JSX (`script-src 'unsafe-eval'`) — both kept safe by the iframe origin isolation + restricted `connect-src`.
|
|
267
267
|
|
|
@@ -316,12 +316,12 @@ Each persona can define an `allowed_mcp_tools` list that restricts which MCP too
|
|
|
316
316
|
|
|
317
317
|
Predefined presets are available for convenience (via CLI `--mcp-tools-preset` or the web UI):
|
|
318
318
|
|
|
319
|
-
| Preset
|
|
320
|
-
|
|
321
|
-
| `default`
|
|
322
|
-
| `worker`
|
|
319
|
+
| Preset | Description |
|
|
320
|
+
| -------------- | ------------------------------------------------------------------------- |
|
|
321
|
+
| `default` | The 25-tool default scoped set (backward compatible) |
|
|
322
|
+
| `worker` | Subset of default — no task creation capabilities |
|
|
323
323
|
| `orchestrator` | Default + task management, session spawning, persona creation, scheduling |
|
|
324
|
-
| `admin`
|
|
324
|
+
| `admin` | Full access to all available tools |
|
|
325
325
|
|
|
326
326
|
Scoped tokens also enforce workspace isolation — agents can only see tasks within their own workspace. Subtasks created by a scoped agent are automatically parented to the agent's own task. Tool calls to non-permitted tools return an error with a descriptive message listing the available tools.
|
|
327
327
|
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGrF,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAGrF,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EACL,iBAAiB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAK1D,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,sBAAsB,EACtB,sBAAsB,EACtB,yBAAyB,EACzB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,kBAAkB,CAAC"}
|
package/dist/mcp-server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAEA,OAAO,IAAI,MAAM,WAAW,CAAC;AAyB7B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAA8B,MAAM,oBAAoB,CAAC;AAyBrF;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IACP,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,KACE,IAAI,CAAC;AAEV,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,kGAAkG;IAClG,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,UAAU,CAAC,EAAE,cAAc,EAAE,EAAE,CAAC;IAChC,6FAA6F;IAC7F,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;;OAMG;IACH,0BAA0B,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,CAAC;CACpF;AAwDD,iGAAiG;AACjG;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,GAClB,UAAU,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAW9C;AAydD;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,EAC9C,WAAW,EAAE,MAAM,GAClB,MAAM,EAAE,CAQV;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC,MAAM,CA8ItE;AA+BD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,EAC7C,SAAS,EAAE,OAAO,GACjB,MAAM,CAWR"}
|
package/dist/mcp-server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { createClient } from "@connectrpc/connect";
|
|
7
7
|
import { createGrpcTransport } from "@connectrpc/connect-node";
|
|
8
|
-
import { grackle, ROOT_TASK_ID, RENDER_TOOL_PREFIX, selectPromotedRenderTools } from "@grackle-ai/common";
|
|
8
|
+
import { grackle, ROOT_TASK_ID, RENDER_TOOL_PREFIX, selectPromotedRenderTools, } from "@grackle-ai/common";
|
|
9
9
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
10
10
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
11
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, isInitializeRequest, McpError, ErrorCode, } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -190,7 +190,9 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
190
190
|
}
|
|
191
191
|
let components;
|
|
192
192
|
try {
|
|
193
|
-
const resp = await grpcClients.orchestration.listComponents({
|
|
193
|
+
const resp = await grpcClients.orchestration.listComponents({
|
|
194
|
+
workspaceId: authContext.workspaceId,
|
|
195
|
+
});
|
|
194
196
|
components = resp.components;
|
|
195
197
|
}
|
|
196
198
|
catch (err) {
|
|
@@ -207,7 +209,12 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
207
209
|
name: toolName,
|
|
208
210
|
description: `Render the "${c.name}" component (v${c.version}) inline. Promoted from this workspace's component registry; its inputSchema is the component's prop schema.`,
|
|
209
211
|
inputSchema: dynamicRenderInputSchema(c.propsSchema),
|
|
210
|
-
annotations: {
|
|
212
|
+
annotations: {
|
|
213
|
+
readOnlyHint: true,
|
|
214
|
+
destructiveHint: false,
|
|
215
|
+
idempotentHint: true,
|
|
216
|
+
openWorldHint: false,
|
|
217
|
+
},
|
|
211
218
|
});
|
|
212
219
|
}
|
|
213
220
|
return defs;
|
|
@@ -220,13 +227,18 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
220
227
|
* by registry ownership, NOT the static/persona allowlist.
|
|
221
228
|
*/
|
|
222
229
|
async function handleDynamicRender(name, rawArgs) {
|
|
223
|
-
const unknownTool = {
|
|
230
|
+
const unknownTool = {
|
|
231
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
232
|
+
isError: true,
|
|
233
|
+
};
|
|
224
234
|
if (authContext.type !== "scoped" || !authContext.workspaceId) {
|
|
225
235
|
return unknownTool;
|
|
226
236
|
}
|
|
227
237
|
let components;
|
|
228
238
|
try {
|
|
229
|
-
const resp = await grpcClients.orchestration.listComponents({
|
|
239
|
+
const resp = await grpcClients.orchestration.listComponents({
|
|
240
|
+
workspaceId: authContext.workspaceId,
|
|
241
|
+
});
|
|
230
242
|
components = resp.components;
|
|
231
243
|
}
|
|
232
244
|
catch (error) {
|
|
@@ -241,7 +253,12 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
241
253
|
// reject a non-object payload (array/string/null) before it reaches the render.
|
|
242
254
|
if (typeof rawArgs !== "object" || rawArgs === null || Array.isArray(rawArgs)) {
|
|
243
255
|
return {
|
|
244
|
-
content: [
|
|
256
|
+
content: [
|
|
257
|
+
{
|
|
258
|
+
type: "text",
|
|
259
|
+
text: JSON.stringify({ error: "arguments must be an object of props", code: "INVALID_ARGUMENT" }, null, 2),
|
|
260
|
+
},
|
|
261
|
+
],
|
|
245
262
|
isError: true,
|
|
246
263
|
};
|
|
247
264
|
}
|
|
@@ -249,7 +266,15 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
249
266
|
const validationErr = propsValidationError(match.propsSchema, props);
|
|
250
267
|
if (validationErr) {
|
|
251
268
|
return {
|
|
252
|
-
content: [
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: JSON.stringify({
|
|
273
|
+
error: `props do not match the component's propsSchema: ${validationErr}`,
|
|
274
|
+
code: "INVALID_ARGUMENT",
|
|
275
|
+
}, null, 2),
|
|
276
|
+
},
|
|
277
|
+
],
|
|
253
278
|
isError: true,
|
|
254
279
|
};
|
|
255
280
|
}
|
|
@@ -282,7 +307,9 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
282
307
|
// path for the standalone MCP server, where the client renders the
|
|
283
308
|
// widget itself.
|
|
284
309
|
const uiOn = publishWidgetEvent !== undefined || hostSupportsUiApps(server.getClientCapabilities());
|
|
285
|
-
const staticTools = uiOn
|
|
310
|
+
const staticTools = uiOn
|
|
311
|
+
? visibleToolDefs
|
|
312
|
+
: visibleToolDefs.filter((t) => !uiToolNames.has(t.name));
|
|
286
313
|
// Dynamic render_<name> tools render only via the in-process broker capture
|
|
287
314
|
// (they have no readable ui:// resource), so list them ONLY when the broker is
|
|
288
315
|
// active — never in standalone/ui-host mode where a host would resources/read
|
|
@@ -341,9 +368,18 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
341
368
|
};
|
|
342
369
|
}
|
|
343
370
|
const available = visibleToolNames;
|
|
344
|
-
logger.warn({
|
|
371
|
+
logger.warn({
|
|
372
|
+
tool: name,
|
|
373
|
+
authType: authContext.type,
|
|
374
|
+
personaId: authContext.type === "scoped" ? authContext.personaId : undefined,
|
|
375
|
+
}, "Tool call rejected by scope: %s", name);
|
|
345
376
|
return {
|
|
346
|
-
content: [
|
|
377
|
+
content: [
|
|
378
|
+
{
|
|
379
|
+
type: "text",
|
|
380
|
+
text: `Tool "${name}" is not permitted for this session. Available tools: ${available}`,
|
|
381
|
+
},
|
|
382
|
+
],
|
|
347
383
|
isError: true,
|
|
348
384
|
};
|
|
349
385
|
}
|
|
@@ -365,7 +401,15 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
365
401
|
: undefined;
|
|
366
402
|
if (requestedWorkspaceId !== undefined && requestedWorkspaceId !== boundWorkspace) {
|
|
367
403
|
return {
|
|
368
|
-
content: [
|
|
404
|
+
content: [
|
|
405
|
+
{
|
|
406
|
+
type: "text",
|
|
407
|
+
text: JSON.stringify({
|
|
408
|
+
error: "workspaceId does not match the authenticated workspace",
|
|
409
|
+
code: "PERMISSION_DENIED",
|
|
410
|
+
}, null, 2),
|
|
411
|
+
},
|
|
412
|
+
],
|
|
369
413
|
isError: true,
|
|
370
414
|
};
|
|
371
415
|
}
|
|
@@ -382,12 +426,20 @@ async function createMcpServerInstance(grpcClients, authContext, assetBaseUrl, b
|
|
|
382
426
|
}
|
|
383
427
|
// Enforce workspace scoping: verify task belongs to the caller's workspace.
|
|
384
428
|
// Skip check when caller has no workspace (root task agents can see any task).
|
|
385
|
-
if (name === "task_show" &&
|
|
429
|
+
if (name === "task_show" &&
|
|
430
|
+
authContext.workspaceId &&
|
|
431
|
+
typeof rawArgs.taskId === "string" &&
|
|
432
|
+
rawArgs.taskId) {
|
|
386
433
|
try {
|
|
387
434
|
const task = await grpcClients.orchestration.getTask({ id: rawArgs.taskId });
|
|
388
435
|
if ((task.workspaceId || undefined) !== authContext.workspaceId) {
|
|
389
436
|
return {
|
|
390
|
-
content: [
|
|
437
|
+
content: [
|
|
438
|
+
{
|
|
439
|
+
type: "text",
|
|
440
|
+
text: JSON.stringify({ error: "Task belongs to a different workspace", code: "PERMISSION_DENIED" }, null, 2),
|
|
441
|
+
},
|
|
442
|
+
],
|
|
391
443
|
isError: true,
|
|
392
444
|
};
|
|
393
445
|
}
|
|
@@ -460,7 +512,7 @@ export function sessionIdsForWorkspace(authContexts, workspaceId) {
|
|
|
460
512
|
* and Server instance, tracked by session ID.
|
|
461
513
|
*/
|
|
462
514
|
export function createMcpServer(options) {
|
|
463
|
-
const { bindHost, mcpPort, grpcPort, apiKey, authorizationServerUrl, toolGroups, publishWidgetEvent, mcpOrigin, onComponentChangeSubscribe } = options;
|
|
515
|
+
const { bindHost, mcpPort, grpcPort, apiKey, authorizationServerUrl, toolGroups, publishWidgetEvent, mcpOrigin, onComponentChangeSubscribe, } = options;
|
|
464
516
|
// Trusted, browser-facing MCP origin for broker-captured widgets (their
|
|
465
517
|
// `<script src>` + CSP allowlist). Derived from server config — NOT the request
|
|
466
518
|
// `Host` header, which a hostile MCP client could spoof to point widget assets
|
|
@@ -469,12 +521,10 @@ export function createMcpServer(options) {
|
|
|
469
521
|
const dialableMcpHost = bindHost === "0.0.0.0" || bindHost === "::" ? "127.0.0.1" : bindHost;
|
|
470
522
|
const brokerAssetOrigin = mcpOrigin ?? `http://${dialableMcpHost}:${mcpPort}`;
|
|
471
523
|
/** Parsed auth server URL, used for dynamic derivation of authorization_servers. */
|
|
472
|
-
const parsedAuthServerUrl = authorizationServerUrl
|
|
473
|
-
? new URL(authorizationServerUrl)
|
|
474
|
-
: undefined;
|
|
524
|
+
const parsedAuthServerUrl = authorizationServerUrl ? new URL(authorizationServerUrl) : undefined;
|
|
475
525
|
/** Effective port (explicit or protocol default). */
|
|
476
526
|
const authServerPort = parsedAuthServerUrl
|
|
477
|
-
?
|
|
527
|
+
? parsedAuthServerUrl.port || (parsedAuthServerUrl.protocol === "https:" ? "443" : "80")
|
|
478
528
|
: undefined;
|
|
479
529
|
/** Scheme to use for derived auth URLs (preserves https when configured). */
|
|
480
530
|
const authServerScheme = parsedAuthServerUrl?.protocol ?? "http:";
|
|
@@ -494,7 +544,9 @@ export function createMcpServer(options) {
|
|
|
494
544
|
for (const sid of sessionIdsForWorkspace(authContexts, workspaceId)) {
|
|
495
545
|
const srv = mcpServers.get(sid);
|
|
496
546
|
if (srv) {
|
|
497
|
-
srv
|
|
547
|
+
srv
|
|
548
|
+
.sendToolListChanged()
|
|
549
|
+
.catch((err) => logger.warn({ sessionId: sid, err }, "sendToolListChanged failed (non-fatal)"));
|
|
498
550
|
}
|
|
499
551
|
}
|
|
500
552
|
};
|
|
@@ -602,7 +654,9 @@ async function parseBody(req) {
|
|
|
602
654
|
* Host header is missing or unparseable.
|
|
603
655
|
*/
|
|
604
656
|
export function resolveAssetBaseUrl(hostHeader, forwardedProto, encrypted) {
|
|
605
|
-
const rawProto = Array.isArray(forwardedProto)
|
|
657
|
+
const rawProto = Array.isArray(forwardedProto)
|
|
658
|
+
? forwardedProto[0]
|
|
659
|
+
: forwardedProto;
|
|
606
660
|
const proto = rawProto?.split(",")[0]?.trim();
|
|
607
661
|
const scheme = proto && proto.length > 0 ? proto : encrypted ? "https" : "http";
|
|
608
662
|
try {
|