#84 - Projects support in MCP, A2A, API
deci committed
Dec 10, 2025 at 16:05 UTC
d1be7f25319ffae55d92d47a6c6042810b3403b1
7 files changed
+434
-39
docs/connectivity.md
+93
@@ -25,6 +25,7 @@ Send messages to Agent Zero and receive responses. Supports text messages, file
25
* `message` (string, required): The message to send
26
* `attachments` (array, optional): Array of `{filename, base64}` objects
27
* `lifetime_hours` (number, optional): Chat lifetime in hours (default: 24)
28
+* `project` (string, optional): Project name to activate (only on first message)
29
30
**Headers:**
31
* `X-API-KEY` (required)
@@ -169,6 +170,63 @@ async function sendWithAttachment() {
170
sendWithAttachment();
171
```
172
173
+#### Project Usage Example
174
+
175
+```javascript
176
+// Working with projects
177
+async function sendMessageWithProject() {
178
+ try {
179
+ // First message - activate project
180
+ const response = await fetch('YOUR_AGENT_ZERO_URL/api_message', {
181
+ method: 'POST',
182
+ headers: {
183
+ 'Content-Type': 'application/json',
184
+ 'X-API-KEY': 'YOUR_API_KEY'
185
+ },
186
+ body: JSON.stringify({
187
+ message: "Analyze the project structure",
188
+ project: "my-web-app" // Activates this project
189
+ })
190
+ });
191
+
192
+ const data = await response.json();
193
+
194
+ if (response.ok) {
195
+ console.log('✅ Project activated!');
196
+ console.log('Context ID:', data.context_id);
197
+ console.log('Response:', data.response);
198
+
199
+ // Continue conversation - project already set
200
+ const followUp = await fetch('YOUR_AGENT_ZERO_URL/api_message', {
201
+ method: 'POST',
202
+ headers: {
203
+ 'Content-Type': 'application/json',
204
+ 'X-API-KEY': 'YOUR_API_KEY'
205
+ },
206
+ body: JSON.stringify({
207
+ context_id: data.context_id,
208
+ message: "What files are in the project?"
209
+ // Do NOT include project field here - already set on first message
210
+ })
211
+ });
212
+
213
+ const followUpData = await followUp.json();
214
+ console.log('Follow-up response:', followUpData.response);
215
+ return followUpData;
216
+ } else {
217
+ console.error('❌ Error:', data.error);
218
+ return null;
219
+ }
220
+ } catch (error) {
221
+ console.error('❌ Request failed:', error);
222
+ return null;
223
+ }
224
+}
225
+
226
+// Call the function
227
+sendMessageWithProject();
228
+```
229
+
230
---
231
232
## `GET/POST /api_log_get`
@@ -568,6 +626,30 @@ Below is an example of a `mcp.json` configuration file that a client could use t
626
}
627
```
628
629
+### Project Support in MCP
630
+
631
+You can specify a project for MCP connections by including it in the URL path:
632
+
633
+```json
634
+{
635
+ "mcpServers": {
636
+ "agent-zero-with-project": {
637
+ "type": "sse",
638
+ "url": "YOUR_AGENT_ZERO_URL/mcp/t-YOUR_API_TOKEN/p-my-project-name/sse"
639
+ },
640
+ "agent-zero-http-with-project": {
641
+ "type": "streamable-http",
642
+ "url": "YOUR_AGENT_ZERO_URL/mcp/t-YOUR_API_TOKEN/p-my-project-name/http/"
643
+ }
644
+ }
645
+}
646
+```
647
+
648
+When a project is specified in the URL:
649
+- All new chats will be created within that project context
650
+- The agent will have access to project-specific instructions, knowledge, and file structure
651
+- Attempting to use an existing chat_id from a different project will result in an error
652
+
653
---
654
655
## A2A (Agent-to-Agent) Connectivity
@@ -583,3 +665,14 @@ To connect another agent to your Agent Zero instance, use the following URL form
665
```
666
YOUR_AGENT_ZERO_URL/a2a/t-YOUR_API_TOKEN
667
```
668
+
669
+To connect with a specific project active:
670
+
671
+```
672
+YOUR_AGENT_ZERO_URL/a2a/t-YOUR_API_TOKEN/p-PROJECT_NAME
673
+```
674
+
675
+When a project is specified:
676
+- All A2A conversations will run in the context of that project
677
+- The agent will have access to project-specific resources, instructions, and knowledge
678
+- This enables project-isolated agent-to-agent communication
python/api/api_message.py
+14
-1
@@ -3,7 +3,7 @@ import os
3
from datetime import datetime, timedelta
4
from agent import AgentContext, UserMessage, AgentContextType
5
from python.helpers.api import ApiHandler, Request, Response
6
-from python.helpers import files
6
+from python.helpers import files, projects
7
from python.helpers.print_style import PrintStyle
8
from werkzeug.utils import secure_filename
9
from initialize import initialize_agent
@@ -33,6 +33,7 @@ class ApiMessage(ApiHandler):
33
message = input.get("message", "")
34
attachments = input.get("attachments", [])
35
lifetime_hours = input.get("lifetime_hours", 24) # Default 24 hours
36
+ project = input.get("project", None) # Optional project name
37
38
if not message:
39
return Response('{"error": "Message is required"}', status=400, mimetype="application/json")
@@ -71,12 +72,24 @@ class ApiMessage(ApiHandler):
72
context = AgentContext.use(context_id)
73
if not context:
74
return Response('{"error": "Context not found"}', status=404, mimetype="application/json")
75
+
76
+ # Validation: if project is provided but context already has different project
77
+ existing_project = context.get_data(projects.CONTEXT_DATA_KEY_PROJECT)
78
+ if project and existing_project and existing_project != project:
79
+ return Response('{"error": "Project can only be set on first message"}', status=400, mimetype="application/json")
80
else:
81
config = initialize_agent()
82
context = AgentContext(config=config, type=AgentContextType.USER)
83
AgentContext.use(context.id)
84
context_id = context.id
85
86
+ # Activate project if provided
87
+ if project:
88
+ try:
89
+ projects.activate_project(context_id, project)
90
+ except Exception as e:
91
+ return Response(f'{{"error": "Failed to activate project: {str(e)}"}}', status=400, mimetype="application/json")
92
+
93
# Update chat lifetime
94
with self._cleanup_lock:
95
self._chat_lifetimes[context_id] = datetime.now() + timedelta(hours=lifetime_hours)
python/helpers/fasta2a_server.py
+29
-1
@@ -5,8 +5,9 @@ import atexit
5
from typing import Any, List
6
import contextlib
7
import threading
8
+import contextvars
9
9
-from python.helpers import settings
10
+from python.helpers import settings, projects
11
from starlette.requests import Request
12
13
# Local imports
@@ -60,6 +61,9 @@ except ImportError: # pragma: no cover – library not installed
61
62
_PRINTER = PrintStyle(italic=True, font_color="purple", padding=False)
63
64
+# Context variable to store project name from URL
65
+_a2a_project_name: contextvars.ContextVar[str | None] = contextvars.ContextVar('a2a_project_name', default=None)
66
+
67
68
class AgentZeroWorker(Worker): # type: ignore[misc]
69
"""Agent Zero implementation of FastA2A Worker."""
@@ -84,6 +88,16 @@ class AgentZeroWorker(Worker): # type: ignore[misc]
88
cfg = initialize_agent()
89
context = AgentContext(cfg, type=AgentContextType.BACKGROUND)
90
91
+ # Activate project if specified in URL
92
+ project_name = _a2a_project_name.get()
93
+ if project_name:
94
+ try:
95
+ projects.activate_project(context.id, project_name)
96
+ _PRINTER.print(f"[A2A] Activated project: {project_name}")
97
+ except Exception as e:
98
+ _PRINTER.print(f"[A2A] Failed to activate project: {e}")
99
+ raise Exception(f"Failed to activate project: {str(e)}")
100
+
101
# Log user message so it appears instantly in UI chat window
102
context.log.log(
103
type="user", # type: ignore[arg-type]
@@ -424,6 +438,9 @@ class DynamicA2AProxy:
438
if path.startswith('/a2a'):
439
path = path[4:] # Remove '/a2a' prefix
440
441
+ # Initialize project name
442
+ project_name = None
443
+
444
# Check if path matches token pattern /t-{token}/
445
if path.startswith('/t-'):
446
# Extract token from path
@@ -431,6 +448,14 @@ class DynamicA2AProxy:
448
path_parts = path[3:].split('/', 1) # Remove '/t-' prefix
449
request_token = path_parts[0]
450
remaining_path = '/' + path_parts[1] if len(path_parts) > 1 else '/'
451
+
452
+ # Check for project pattern /p-{project}/
453
+ if remaining_path.startswith('/p-'):
454
+ project_parts = remaining_path[3:].split('/', 1)
455
+ if project_parts[0]:
456
+ project_name = project_parts[0]
457
+ remaining_path = '/' + project_parts[1] if len(project_parts) > 1 else '/'
458
+ _PRINTER.print(f"[A2A] Extracted project from URL: {project_name}")
459
else:
460
request_token = path[3:]
461
remaining_path = '/'
@@ -452,6 +477,9 @@ class DynamicA2AProxy:
477
})
478
return
479
480
+ # Set project name in context variable for use by worker
481
+ _a2a_project_name.set(project_name)
482
+
483
# Update scope with cleaned path
484
scope = dict(scope)
485
scope['path'] = remaining_path
python/helpers/mcp_server.py
+127
-12
@@ -3,23 +3,31 @@ from typing import Annotated, Literal, Union
3
from urllib.parse import urlparse
4
from openai import BaseModel
5
from pydantic import Field
6
-from fastmcp import FastMCP
6
+from fastmcp import FastMCP # type: ignore
7
+import contextvars
8
9
from agent import AgentContext, AgentContextType, UserMessage
10
from python.helpers.persist_chat import remove_chat
11
from initialize import initialize_agent
12
from python.helpers.print_style import PrintStyle
12
-from python.helpers import settings
13
+from python.helpers import settings, projects
14
from starlette.middleware import Middleware
15
from starlette.middleware.base import BaseHTTPMiddleware
16
from starlette.exceptions import HTTPException as StarletteHTTPException
17
from starlette.types import ASGIApp, Receive, Scope, Send
17
-from fastmcp.server.http import create_sse_app
18
+from fastmcp.server.http import create_sse_app # type: ignore
19
from starlette.requests import Request
20
import threading
21
22
_PRINTER = PrintStyle(italic=True, font_color="green", padding=False)
23
24
+# Context variable to store project name from URL (per-request)
25
+_mcp_project_name: contextvars.ContextVar[str | None] = contextvars.ContextVar('mcp_project_name', default=None)
26
+
27
+# Session storage for project names (persists across SSE tool calls)
28
+# Key: connection identifier, Value: project name
29
+_mcp_project_sessions: dict[str, str | None] = {}
30
+_mcp_session_lock = threading.Lock()
31
32
mcp_server: FastMCP = FastMCP(
33
name="Agent Zero integrated MCP Server",
@@ -127,6 +135,25 @@ async def send_message(
135
description="The response from the remote Agent Zero Instance", title="response"
136
),
137
]:
138
+ # Get project name from session storage (persists across SSE connection)
139
+ # First try context variable (for HTTP requests), then session storage (for SSE tool calls)
140
+ project_name = _mcp_project_name.get()
141
+
142
+ # If not in context variable, try session storage using current token
143
+ if not project_name:
144
+ cfg = settings.get_settings()
145
+ current_token = cfg.get("mcp_server_token")
146
+ _PRINTER.print(f"[MCP] send_message - Looking for project. Token: '{current_token}' (type: {type(current_token).__name__})")
147
+ if current_token:
148
+ with _mcp_session_lock:
149
+ _PRINTER.print(f"[MCP] Session storage keys: {list(_mcp_project_sessions.keys())}")
150
+ _PRINTER.print(f"[MCP] Session storage: {_mcp_project_sessions}")
151
+ project_name = _mcp_project_sessions.get(current_token)
152
+ if project_name:
153
+ _PRINTER.print(f"[MCP] Retrieved project from session: {project_name}")
154
+ else:
155
+ _PRINTER.print(f"[MCP] No project found in session for token: {current_token}")
156
+
157
context: AgentContext | None = None
158
if chat_id:
159
context = AgentContext.get(chat_id)
@@ -137,10 +164,27 @@ async def send_message(
164
# whether we should save the chat or delete it afterwards
165
# If we continue a conversation, it must be persistent
166
persistent_chat = True
167
+
168
+ # Validation: if project is in URL but context has different project
169
+ if project_name:
170
+ existing_project = context.get_data(projects.CONTEXT_DATA_KEY_PROJECT)
171
+ if existing_project and existing_project != project_name:
172
+ return ToolError(
173
+ error=f"Chat belongs to project '{existing_project}' but URL specifies '{project_name}'",
174
+ chat_id=chat_id
175
+ )
176
else:
177
config = initialize_agent()
178
context = AgentContext(config=config, type=AgentContextType.BACKGROUND)
179
180
+ # Activate project if specified in URL
181
+ if project_name:
182
+ try:
183
+ projects.activate_project(context.id, project_name)
184
+ _PRINTER.print(f"[MCP] Activated project: {project_name}")
185
+ except Exception as e:
186
+ return ToolError(error=f"Failed to activate project: {str(e)}", chat_id="")
187
+
188
if not message:
189
return ToolError(
190
error="Message is required", chat_id=context.id if persistent_chat else ""
@@ -325,10 +369,10 @@ class DynamicMcpProxy:
369
370
def _create_custom_http_app(self, streamable_http_path, auth_server_provider, auth_settings, debug, routes):
371
"""Create a custom HTTP app that manages the session manager manually."""
328
- from fastmcp.server.http import setup_auth_middleware_and_routes, create_base_app
329
- from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
372
+ from fastmcp.server.http import setup_auth_middleware_and_routes, create_base_app # type: ignore
373
+ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager # type: ignore
374
from starlette.routing import Mount
331
- from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
375
+ from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware # type: ignore
376
import anyio
377
378
server_routes = []
@@ -408,12 +452,47 @@ class DynamicMcpProxy:
452
# Route based on path
453
path = scope.get("path", "")
454
411
- if f"/t-{self.token}/sse" in path or f"t-{self.token}/messages" in path:
412
- # Route to SSE app
413
- await sse_app(scope, receive, send)
414
- elif f"/t-{self.token}/http" in path:
415
- # Route to HTTP app
416
- await http_app(scope, receive, send)
455
+ # Check for token in path (with or without project segment)
456
+ # Patterns: /t-{token}/sse, /t-{token}/p-{project}/sse, etc.
457
+ has_token = f"/t-{self.token}/" in path or f"t-{self.token}/" in path
458
+
459
+ # Extract project from path BEFORE cleaning (for session storage)
460
+ project_name = None
461
+ if "/p-" in path:
462
+ try:
463
+ parts = path.split("/p-")
464
+ if len(parts) > 1:
465
+ project_part = parts[1].split("/")[0]
466
+ if project_part:
467
+ project_name = project_part
468
+ _PRINTER.print(f"[MCP] Proxy extracted project from URL: {project_name}")
469
+ except Exception as e:
470
+ _PRINTER.print(f"[MCP] Failed to extract project in proxy: {e}")
471
+
472
+ # Store project in session (persists across SSE connection)
473
+ if self.token and project_name:
474
+ with _mcp_session_lock:
475
+ _mcp_project_sessions[self.token] = project_name
476
+ _PRINTER.print(f"[MCP] Stored project '{project_name}' for token '{self.token}' (type: {type(self.token).__name__}) in proxy")
477
+
478
+ # Strip project segment from path if present (e.g., /p-project_name/)
479
+ # This is needed because the underlying MCP apps were configured without project paths
480
+ cleaned_path = path
481
+ if "/p-" in path:
482
+ # Remove /p-{project}/ segment: /t-TOKEN/p-PROJECT/sse -> /t-TOKEN/sse
483
+ import re
484
+ cleaned_path = re.sub(r'/p-[^/]+/', '/', path)
485
+
486
+ # Update scope with cleaned path for the underlying app
487
+ modified_scope = dict(scope)
488
+ modified_scope['path'] = cleaned_path
489
+
490
+ if has_token and ("/sse" in path or "/messages" in path):
491
+ # Route to SSE app with cleaned path
492
+ await sse_app(modified_scope, receive, send)
493
+ elif has_token and "/http" in path:
494
+ # Route to HTTP app with cleaned path
495
+ await http_app(modified_scope, receive, send)
496
else:
497
raise StarletteHTTPException(
498
status_code=403, detail="MCP forbidden"
@@ -430,4 +509,40 @@ async def mcp_middleware(request: Request, call_next):
509
status_code=403, detail="MCP server is disabled in settings."
510
)
511
512
+ # Extract project from URL path if present (pattern: /mcp/t-{token}/p-{project}/...)
513
+ path = request.url.path
514
+ project_name = None
515
+ token = None
516
+
517
+ # Extract token from path
518
+ if "/t-" in path:
519
+ token_parts = path.split("/t-")
520
+ if len(token_parts) > 1:
521
+ token = token_parts[1].split("/")[0]
522
+
523
+ # Extract project if present
524
+ if "/p-" in path:
525
+ try:
526
+ parts = path.split("/p-")
527
+ if len(parts) > 1:
528
+ project_part = parts[1].split("/")[0]
529
+ if project_part:
530
+ project_name = project_part
531
+ _PRINTER.print(f"[MCP] Extracted project from URL: {project_name}")
532
+ except Exception as e:
533
+ _PRINTER.print(f"[MCP] Failed to extract project from URL: {e}")
534
+
535
+ # Debug logging
536
+ _PRINTER.print(f"[MCP] Middleware - Path: {path}, Token: {token}, Project: {project_name}")
537
+
538
+ # Store project in session dict ONLY if we found one (don't overwrite with None)
539
+ # The proxy already handles project extraction before path cleaning
540
+ if token and project_name:
541
+ with _mcp_session_lock:
542
+ _mcp_project_sessions[token] = project_name
543
+ _PRINTER.print(f"[MCP] Middleware stored project '{project_name}' for token session")
544
+
545
+ # Also set in context variable for backwards compatibility
546
+ _mcp_project_name.set(project_name)
547
+
548
return await call_next(request)
webui/components/settings/a2a/a2a-connection.html
+49
-4
@@ -21,10 +21,21 @@
21
</div>
22
23
<h3>A2A Connection URL</h3>
24
+
25
+ <!-- Project selector -->
26
+ <div style="margin: 16px 0;">
27
+ <label for="a2a-project-select" style="display: block; margin-bottom: 8px; color: var(--color-text-primary);">
28
+ Select Project (optional):
29
+ </label>
30
+ <select id="a2a-project-select" style="width: 100%; padding: 8px; background-color: var(--color-bg-secondary); color: var(--color-text-primary); border: 1px solid var(--color-border); border-radius: 4px;">
31
+ <option value="">No project</option>
32
+ </select>
33
+ </div>
34
+
35
<div id="a2a-connection-example"></div>
36
37
<script>
27
- setTimeout(() => {
38
+ setTimeout(async () => {
39
const url = window.location.origin;
40
// Try to get a2a_token first, fallback to mcp_server_token
41
let tokenField = null;
@@ -33,15 +44,49 @@
44
tokenField = allFields.find(f => f.id === 'a2a_token') || allFields.find(f => f.id === 'mcp_server_token');
45
} catch (e) { }
46
const token = tokenField ? tokenField.value : '';
36
- const connectionUrl = `${url}/a2a/t-${token}`;
47
48
+ // Fetch and populate projects
49
+ const projectSelect = document.getElementById('a2a-project-select');
50
+ try {
51
+ const response = await fetch('/projects', {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify({ action: 'list' })
55
+ });
56
+ const data = await response.json();
57
+ if (data.ok && data.data) {
58
+ data.data.forEach(project => {
59
+ const option = document.createElement('option');
60
+ option.value = project.name;
61
+ option.textContent = project.title || project.name;
62
+ projectSelect.appendChild(option);
63
+ });
64
+ }
65
+ } catch (e) {
66
+ console.error('Failed to load projects:', e);
67
+ }
68
+
69
+ // Function to update URL based on selected project
70
+ function updateConnectionUrl() {
71
+ const selectedProject = projectSelect.value;
72
+ let connectionUrl = `${url}/a2a/t-${token}`;
73
+ if (selectedProject) {
74
+ connectionUrl += `/p-${selectedProject}`;
75
+ }
76
+ editor.setValue(connectionUrl);
77
+ editor.clearSelection();
78
+ }
79
+
80
+ // Initialize editor
81
const editor = ace.edit("a2a-connection-example");
82
const dark = localStorage.getItem("darkMode");
83
editor.setTheme(dark !== "false" ? "ace/theme/github_dark" : "ace/theme/tomorrow");
84
editor.session.setMode("ace/mode/text");
42
- editor.setValue(connectionUrl);
43
- editor.clearSelection();
85
+ updateConnectionUrl();
86
editor.setReadOnly(true);
87
+
88
+ // Update URL when project selection changes
89
+ projectSelect.addEventListener('change', updateConnectionUrl);
90
}, 0);
91
</script>
92
</div>
webui/components/settings/external/api-examples.html
+61
-1
@@ -34,7 +34,8 @@
34
• <code>context_id</code> (string, optional): Existing chat context ID<br>
35
• <code>message</code> (string, required): The message to send<br>
36
• <code>attachments</code> (array, optional): Array of {filename, base64} objects<br>
37
- • <code>lifetime_hours</code> (number, optional): Chat lifetime in hours (default: 24)
37
+ • <code>lifetime_hours</code> (number, optional): Chat lifetime in hours (default: 24)<br>
38
+ • <code>project</code> (string, optional): Project name to activate (only on first message)
39
</p>
40
<p style="margin: 0; color: var(--color-text-secondary); font-size: 14px;">
41
<strong>Headers:</strong> <code>X-API-KEY</code> (required), <code>Content-Type: application/json</code>
@@ -52,6 +53,9 @@
53
54
<h4>File Attachment Example</h4>
55
<div id="api-attachment-example"></div>
56
+
57
+ <h4>Project Usage Example</h4>
58
+ <div id="api-project-example"></div>
59
</div>
60
61
<!-- Section 2: api_log_get Endpoint -->
@@ -602,11 +606,66 @@ async function attachmentWorkflow() {
606
// Run the complete workflow
607
attachmentWorkflow();`;
608
609
+ // Project usage example
610
+ const projectExample = `// Working with projects
611
+async function sendMessageWithProject() {
612
+ try {
613
+ // First message - activate project
614
+ const response = await fetch('${url}/api_message', {
615
+ method: 'POST',
616
+ headers: {
617
+ 'Content-Type': 'application/json',
618
+ 'X-API-KEY': '${token}'
619
+ },
620
+ body: JSON.stringify({
621
+ message: "Analyze the project structure",
622
+ project: "my-web-app" // Activates this project
623
+ })
624
+ });
625
+
626
+ const data = await response.json();
627
+
628
+ if (response.ok) {
629
+ console.log('✅ Project activated!');
630
+ console.log('Context ID:', data.context_id);
631
+ console.log('Response:', data.response);
632
+
633
+ // Continue conversation - project already set
634
+ const followUp = await fetch('${url}/api_message', {
635
+ method: 'POST',
636
+ headers: {
637
+ 'Content-Type': 'application/json',
638
+ 'X-API-KEY': '${token}'
639
+ },
640
+ body: JSON.stringify({
641
+ context_id: data.context_id,
642
+ message: "What files are in the project?"
643
+ // Do NOT include project field here - already set on first message
644
+ })
645
+ });
646
+
647
+ const followUpData = await followUp.json();
648
+ console.log('Follow-up response:', followUpData.response);
649
+ return followUpData;
650
+ } else {
651
+ console.error('❌ Error:', data.error);
652
+ return null;
653
+ }
654
+ } catch (error) {
655
+ console.error('❌ Request failed:', error);
656
+ return null;
657
+ }
658
+}
659
+
660
+// Call the function
661
+sendMessageWithProject();`;
662
+
663
// Initialize ACE editors
664
const editors = [
665
{ id: "api-basic-example", content: basicExample },
666
{ id: "api-continuation-example", content: continuationExample },
667
{ id: "api-attachment-example", content: attachmentExample },
668
+ { id: "api-project-example", content: projectExample },
669
{ id: "api-log-get-example", content: logGetExample },
670
{ id: "api-log-post-example", content: logPostExample },
671
{ id: "api-terminate-example", content: terminateExample },
@@ -635,6 +694,7 @@ attachmentWorkflow();`;
694
#api-basic-example,
695
#api-continuation-example,
696
#api-attachment-example,
697
+ #api-project-example,
698
#api-log-get-example,
699
#api-log-post-example,
700
#api-terminate-example,
webui/components/settings/mcp/server/example.html
+61
-20
@@ -20,37 +20,78 @@
20
</div>
21
22
<h3>Example MCP Server Configuration JSON</h3>
23
+
24
+ <!-- Project selector -->
25
+ <div style="margin: 16px 0;">
26
+ <label for="mcp-project-select" style="display: block; margin-bottom: 8px; color: var(--color-text-primary);">
27
+ Select Project (optional):
28
+ </label>
29
+ <select id="mcp-project-select" style="width: 100%; padding: 8px; background-color: var(--color-bg-secondary); color: var(--color-text-primary); border: 1px solid var(--color-border); border-radius: 4px;">
30
+ <option value="">No project</option>
31
+ </select>
32
+ </div>
33
+
34
<div id="mcp-server-example"></div>
35
36
<script>
26
- setTimeout(() => {
37
+ setTimeout(async () => {
38
const url = window.location.origin;
39
const token = settingsModalProxy.settings.sections.filter(x => x.id == "mcp_server")[0].fields.filter(x => x.id == "mcp_server_token")[0].value;
29
- const jsonExample = JSON.stringify({
30
- "mcpServers":
31
- {
32
- "agent-zero": {
33
- "type": "sse",
34
- "url": `${url}/mcp/t-${token}/sse`
35
- },
36
- "agent-zero-http": {
37
- "type": "streamable-http",
38
- "url": `${url}/mcp/t-${token}/http`
39
- }
40
+
41
+ // Fetch and populate projects
42
+ const projectSelect = document.getElementById('mcp-project-select');
43
+ try {
44
+ const response = await fetch('/projects', {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({ action: 'list' })
48
+ });
49
+ const data = await response.json();
50
+ if (data.ok && data.data) {
51
+ data.data.forEach(project => {
52
+ const option = document.createElement('option');
53
+ option.value = project.name;
54
+ option.textContent = project.title || project.name;
55
+ projectSelect.appendChild(option);
56
+ });
57
}
41
- }, null, 2);
58
+ } catch (e) {
59
+ console.error('Failed to load projects:', e);
60
+ }
61
+
62
+ // Function to update JSON based on selected project
63
+ function updateMcpConfig() {
64
+ const selectedProject = projectSelect.value;
65
+ const projectPath = selectedProject ? `/p-${selectedProject}` : '';
66
67
+ const jsonExample = JSON.stringify({
68
+ "mcpServers":
69
+ {
70
+ "agent-zero": {
71
+ "type": "sse",
72
+ "url": `${url}/mcp/t-${token}${projectPath}/sse`
73
+ },
74
+ "agent-zero-http": {
75
+ "type": "streamable-http",
76
+ "url": `${url}/mcp/t-${token}${projectPath}/http`
77
+ }
78
+ }
79
+ }, null, 2);
80
+
81
+ editor.setValue(jsonExample);
82
+ editor.clearSelection();
83
+ }
84
+
85
+ // Initialize editor
86
const editor = ace.edit("mcp-server-example");
87
const dark = localStorage.getItem("darkMode");
45
- if (dark != "false") {
46
- editor.setTheme("ace/theme/github_dark");
47
- } else {
48
- editor.setTheme("ace/theme/tomorrow");
49
- }
88
+ editor.setTheme(dark !== "false" ? "ace/theme/github_dark" : "ace/theme/tomorrow");
89
editor.session.setMode("ace/mode/json");
51
- editor.setValue(jsonExample);
52
- editor.clearSelection();
90
+ updateMcpConfig();
91
editor.setReadOnly(true);
92
+
93
+ // Update JSON when project selection changes
94
+ projectSelect.addEventListener('change', updateMcpConfig);
95
}, 0);
96
</script>
97
<!-- </template> -->