universal-mcp 0.1.1__py3-none-any.whl → 0.1.2__py3-none-any.whl
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.
- universal_mcp/applications/__init__.py +23 -28
- universal_mcp/applications/application.py +13 -8
- universal_mcp/applications/e2b/app.py +74 -0
- universal_mcp/applications/firecrawl/app.py +381 -0
- universal_mcp/applications/github/README.md +35 -0
- universal_mcp/applications/github/app.py +133 -100
- universal_mcp/applications/google_calendar/app.py +170 -139
- universal_mcp/applications/google_mail/app.py +185 -160
- universal_mcp/applications/markitdown/app.py +32 -0
- universal_mcp/applications/notion/README.md +32 -0
- universal_mcp/applications/notion/__init__.py +0 -0
- universal_mcp/applications/notion/app.py +415 -0
- universal_mcp/applications/reddit/app.py +112 -71
- universal_mcp/applications/resend/app.py +3 -8
- universal_mcp/applications/serp/app.py +84 -0
- universal_mcp/applications/tavily/app.py +11 -10
- universal_mcp/applications/zenquotes/app.py +3 -3
- universal_mcp/cli.py +98 -16
- universal_mcp/config.py +20 -3
- universal_mcp/exceptions.py +1 -3
- universal_mcp/integrations/__init__.py +6 -2
- universal_mcp/integrations/agentr.py +26 -24
- universal_mcp/integrations/integration.py +72 -35
- universal_mcp/servers/__init__.py +21 -1
- universal_mcp/servers/server.py +77 -44
- universal_mcp/stores/__init__.py +15 -2
- universal_mcp/stores/store.py +123 -13
- universal_mcp/utils/__init__.py +1 -0
- universal_mcp/utils/api_generator.py +269 -0
- universal_mcp/utils/docgen.py +360 -0
- universal_mcp/utils/installation.py +17 -2
- universal_mcp/utils/openapi.py +216 -111
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2.dist-info}/METADATA +23 -5
- universal_mcp-0.1.2.dist-info/RECORD +40 -0
- universal_mcp-0.1.1.dist-info/RECORD +0 -29
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.1.dist-info → universal_mcp-0.1.2.dist-info}/entry_points.txt +0 -0
universal_mcp/cli.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
-
import
|
1
|
+
import asyncio
|
2
|
+
import os
|
2
3
|
from pathlib import Path
|
3
4
|
|
5
|
+
import typer
|
6
|
+
|
4
7
|
from universal_mcp.utils.installation import (
|
5
8
|
get_supported_apps,
|
6
9
|
install_claude,
|
@@ -11,29 +14,111 @@ app = typer.Typer()
|
|
11
14
|
|
12
15
|
|
13
16
|
@app.command()
|
14
|
-
def generate(
|
15
|
-
|
17
|
+
def generate(
|
18
|
+
schema_path: Path = typer.Option(..., "--schema", "-s"),
|
19
|
+
output_path: Path = typer.Option(
|
20
|
+
None,
|
21
|
+
"--output",
|
22
|
+
"-o",
|
23
|
+
help="Output file path - should match the API name (e.g., 'twitter.py' for Twitter API)",
|
24
|
+
),
|
25
|
+
add_docstrings: bool = typer.Option(
|
26
|
+
True, "--docstrings/--no-docstrings", help="Add docstrings to generated code"
|
27
|
+
),
|
28
|
+
):
|
29
|
+
"""Generate API client from OpenAPI schema with optional docstring generation.
|
30
|
+
|
31
|
+
The output filename should match the name of the API in the schema (e.g., 'twitter.py' for Twitter API).
|
32
|
+
This name will be used for the folder in applications/ and as a prefix for function names.
|
33
|
+
"""
|
34
|
+
# Import here to avoid circular imports
|
35
|
+
from universal_mcp.utils.api_generator import generate_api_from_schema
|
36
|
+
|
16
37
|
if not schema_path.exists():
|
17
38
|
typer.echo(f"Error: Schema file {schema_path} does not exist", err=True)
|
18
39
|
raise typer.Exit(1)
|
19
|
-
from .utils.openapi import generate_api_client, load_schema
|
20
40
|
|
21
41
|
try:
|
22
|
-
|
42
|
+
# Run the async function in the event loop
|
43
|
+
result = asyncio.run(
|
44
|
+
generate_api_from_schema(
|
45
|
+
schema_path=schema_path,
|
46
|
+
output_path=output_path,
|
47
|
+
add_docstrings=add_docstrings,
|
48
|
+
)
|
49
|
+
)
|
50
|
+
|
51
|
+
if not output_path:
|
52
|
+
# Print to stdout if no output path
|
53
|
+
print(result["code"])
|
54
|
+
else:
|
55
|
+
typer.echo("API client successfully generated and installed.")
|
56
|
+
if "app_file" in result:
|
57
|
+
typer.echo(f"Application file: {result['app_file']}")
|
58
|
+
if "readme_file" in result and result["readme_file"]:
|
59
|
+
typer.echo(f"Documentation: {result['readme_file']}")
|
23
60
|
except Exception as e:
|
24
|
-
typer.echo(f"Error
|
61
|
+
typer.echo(f"Error generating API client: {e}", err=True)
|
62
|
+
raise typer.Exit(1) from e
|
63
|
+
|
64
|
+
|
65
|
+
@app.command()
|
66
|
+
def docgen(
|
67
|
+
file_path: Path = typer.Argument(..., help="Path to the Python file to process"),
|
68
|
+
model: str = typer.Option(
|
69
|
+
"anthropic/claude-3-5-sonnet-20241022",
|
70
|
+
"--model",
|
71
|
+
"-m",
|
72
|
+
help="Model to use for generating docstrings",
|
73
|
+
),
|
74
|
+
api_key: str = typer.Option(
|
75
|
+
None,
|
76
|
+
"--api-key",
|
77
|
+
help="Anthropic API key (can also be set via ANTHROPIC_API_KEY environment variable)",
|
78
|
+
),
|
79
|
+
):
|
80
|
+
"""Generate docstrings for Python files using LLMs.
|
81
|
+
|
82
|
+
This command uses litellm with structured output to generate high-quality
|
83
|
+
Google-style docstrings for all functions in the specified Python file.
|
84
|
+
"""
|
85
|
+
from universal_mcp.utils.docgen import process_file
|
86
|
+
|
87
|
+
if not file_path.exists():
|
88
|
+
typer.echo(f"Error: File not found: {file_path}", err=True)
|
25
89
|
raise typer.Exit(1)
|
26
|
-
|
27
|
-
|
90
|
+
|
91
|
+
# Set API key if provided
|
92
|
+
if api_key:
|
93
|
+
os.environ["ANTHROPIC_API_KEY"] = api_key
|
94
|
+
|
95
|
+
try:
|
96
|
+
processed = process_file(str(file_path), model)
|
97
|
+
typer.echo(f"Successfully processed {processed} functions")
|
98
|
+
except Exception as e:
|
99
|
+
typer.echo(f"Error: {e}", err=True)
|
100
|
+
import traceback
|
101
|
+
|
102
|
+
traceback.print_exc()
|
103
|
+
raise typer.Exit(1) from e
|
28
104
|
|
29
105
|
|
30
106
|
@app.command()
|
31
|
-
def run(
|
107
|
+
def run(
|
108
|
+
config_path: Path | None = typer.Option(
|
109
|
+
None, "--config", "-c", help="Path to the config file"
|
110
|
+
),
|
111
|
+
):
|
32
112
|
"""Run the MCP server"""
|
33
|
-
from universal_mcp.
|
113
|
+
from universal_mcp.config import ServerConfig
|
114
|
+
from universal_mcp.servers import server_from_config
|
34
115
|
|
35
|
-
|
36
|
-
|
116
|
+
if config_path:
|
117
|
+
config = ServerConfig.model_validate_json(config_path.read_text())
|
118
|
+
else:
|
119
|
+
config = ServerConfig()
|
120
|
+
server = server_from_config(config)
|
121
|
+
server.run(transport=config.transport)
|
37
122
|
|
38
123
|
|
39
124
|
@app.command()
|
@@ -73,10 +158,7 @@ def install(app_name: str = typer.Argument(..., help="Name of app to install")):
|
|
73
158
|
typer.echo("App installed successfully")
|
74
159
|
except Exception as e:
|
75
160
|
typer.echo(f"Error installing app: {e}", err=True)
|
76
|
-
|
77
|
-
|
78
|
-
traceback.print_exc()
|
79
|
-
raise typer.Exit(1)
|
161
|
+
raise typer.Exit(1) from e
|
80
162
|
|
81
163
|
|
82
164
|
if __name__ == "__main__":
|
universal_mcp/config.py
CHANGED
@@ -1,15 +1,32 @@
|
|
1
|
-
from pydantic import BaseModel
|
2
1
|
from typing import Literal
|
3
2
|
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
4
6
|
class StoreConfig(BaseModel):
|
5
|
-
|
7
|
+
name: str = "universal_mcp"
|
8
|
+
type: Literal["memory", "environment", "keyring", "agentr"]
|
9
|
+
|
6
10
|
|
7
11
|
class IntegrationConfig(BaseModel):
|
8
12
|
name: str
|
9
|
-
type: Literal["api_key", "agentr"]
|
13
|
+
type: Literal["api_key", "oauth", "agentr"]
|
10
14
|
credentials: dict | None = None
|
11
15
|
store: StoreConfig | None = None
|
12
16
|
|
17
|
+
|
13
18
|
class AppConfig(BaseModel):
|
14
19
|
name: str
|
15
20
|
integration: IntegrationConfig | None = None
|
21
|
+
actions: list[str] | None = None
|
22
|
+
|
23
|
+
|
24
|
+
class ServerConfig(BaseModel):
|
25
|
+
name: str = "Universal MCP"
|
26
|
+
description: str = "Universal MCP"
|
27
|
+
api_key: str | None = None
|
28
|
+
type: Literal["local", "agentr"] = "agentr"
|
29
|
+
transport: Literal["stdio", "sse", "http"] = "stdio"
|
30
|
+
port: int = 8005
|
31
|
+
apps: list[AppConfig] | None = None
|
32
|
+
store: StoreConfig | None = None
|
universal_mcp/exceptions.py
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
from universal_mcp.integrations.agentr import AgentRIntegration
|
2
|
-
from universal_mcp.integrations.integration import
|
2
|
+
from universal_mcp.integrations.integration import (
|
3
|
+
ApiKeyIntegration,
|
4
|
+
Integration,
|
5
|
+
OAuthIntegration,
|
6
|
+
)
|
3
7
|
|
4
|
-
__all__ = ["AgentRIntegration", "Integration", "ApiKeyIntegration", "OAuthIntegration"]
|
8
|
+
__all__ = ["AgentRIntegration", "Integration", "ApiKeyIntegration", "OAuthIntegration"]
|
@@ -1,38 +1,44 @@
|
|
1
|
-
from universal_mcp.integrations.integration import Integration
|
2
1
|
import os
|
2
|
+
|
3
3
|
import httpx
|
4
4
|
from loguru import logger
|
5
|
+
|
5
6
|
from universal_mcp.exceptions import NotAuthorizedError
|
7
|
+
from universal_mcp.integrations.integration import Integration
|
8
|
+
|
6
9
|
|
7
10
|
class AgentRIntegration(Integration):
|
8
11
|
"""Integration class for AgentR API authentication and authorization.
|
9
|
-
|
12
|
+
|
10
13
|
This class handles API key authentication and OAuth authorization flow for AgentR services.
|
11
|
-
|
14
|
+
|
12
15
|
Args:
|
13
16
|
name (str): Name of the integration
|
14
17
|
api_key (str, optional): AgentR API key. If not provided, will look for AGENTR_API_KEY env var
|
15
18
|
**kwargs: Additional keyword arguments passed to parent Integration class
|
16
|
-
|
19
|
+
|
17
20
|
Raises:
|
18
21
|
ValueError: If no API key is provided or found in environment variables
|
19
22
|
"""
|
23
|
+
|
20
24
|
def __init__(self, name: str, api_key: str = None, **kwargs):
|
21
25
|
super().__init__(name, **kwargs)
|
22
26
|
self.api_key = api_key or os.getenv("AGENTR_API_KEY")
|
23
27
|
if not self.api_key:
|
24
|
-
logger.error(
|
28
|
+
logger.error(
|
29
|
+
"API key for AgentR is missing. Please visit https://agentr.dev to create an API key, then set it as AGENTR_API_KEY environment variable."
|
30
|
+
)
|
25
31
|
raise ValueError("AgentR API key required - get one at https://agentr.dev")
|
26
32
|
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
27
|
-
|
28
|
-
def set_credentials(self, credentials: dict| None = None):
|
33
|
+
|
34
|
+
def set_credentials(self, credentials: dict | None = None):
|
29
35
|
"""Set credentials for the integration.
|
30
|
-
|
36
|
+
|
31
37
|
This method is not implemented for AgentR integration. Instead it redirects to the authorize flow.
|
32
|
-
|
38
|
+
|
33
39
|
Args:
|
34
40
|
credentials (dict | None, optional): Credentials dict (not used). Defaults to None.
|
35
|
-
|
41
|
+
|
36
42
|
Returns:
|
37
43
|
str: Authorization URL from authorize() method
|
38
44
|
"""
|
@@ -41,22 +47,19 @@ class AgentRIntegration(Integration):
|
|
41
47
|
|
42
48
|
def get_credentials(self):
|
43
49
|
"""Get credentials for the integration from the AgentR API.
|
44
|
-
|
50
|
+
|
45
51
|
Makes API request to retrieve stored credentials for this integration.
|
46
|
-
|
52
|
+
|
47
53
|
Returns:
|
48
54
|
dict: Credentials data from API response
|
49
|
-
|
55
|
+
|
50
56
|
Raises:
|
51
57
|
NotAuthorizedError: If credentials are not found (404 response)
|
52
58
|
HTTPError: For other API errors
|
53
59
|
"""
|
54
60
|
response = httpx.get(
|
55
61
|
f"{self.base_url}/api/{self.name}/credentials/",
|
56
|
-
headers={
|
57
|
-
"accept": "application/json",
|
58
|
-
"X-API-KEY": self.api_key
|
59
|
-
}
|
62
|
+
headers={"accept": "application/json", "X-API-KEY": self.api_key},
|
60
63
|
)
|
61
64
|
if response.status_code == 404:
|
62
65
|
action = self.authorize()
|
@@ -67,21 +70,20 @@ class AgentRIntegration(Integration):
|
|
67
70
|
|
68
71
|
def authorize(self):
|
69
72
|
"""Get authorization URL for the integration.
|
70
|
-
|
73
|
+
|
71
74
|
Makes API request to get OAuth authorization URL.
|
72
|
-
|
75
|
+
|
73
76
|
Returns:
|
74
77
|
str: Message containing authorization URL
|
75
|
-
|
78
|
+
|
76
79
|
Raises:
|
77
80
|
HTTPError: If API request fails
|
78
81
|
"""
|
79
82
|
response = httpx.get(
|
80
83
|
f"{self.base_url}/api/{self.name}/authorize/",
|
81
|
-
headers={
|
82
|
-
"X-API-KEY": self.api_key
|
83
|
-
}
|
84
|
+
headers={"X-API-KEY": self.api_key},
|
84
85
|
)
|
85
86
|
response.raise_for_status()
|
86
87
|
url = response.json()
|
87
|
-
|
88
|
+
|
89
|
+
return f"Please ask the user to visit the following url to authorize the application: {url}. Render the url in proper markdown format with a clickable link."
|
@@ -1,21 +1,27 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
|
-
|
2
|
+
|
3
3
|
import httpx
|
4
|
+
from loguru import logger
|
5
|
+
|
6
|
+
from universal_mcp.exceptions import NotAuthorizedError
|
7
|
+
from universal_mcp.stores.store import Store
|
8
|
+
|
4
9
|
|
5
10
|
class Integration(ABC):
|
6
11
|
"""Abstract base class for handling application integrations and authentication.
|
7
|
-
|
12
|
+
|
8
13
|
This class defines the interface for different types of integrations that handle
|
9
14
|
authentication and authorization with external services.
|
10
|
-
|
15
|
+
|
11
16
|
Args:
|
12
17
|
name: The name identifier for this integration
|
13
18
|
store: Optional Store instance for persisting credentials and other data
|
14
|
-
|
19
|
+
|
15
20
|
Attributes:
|
16
21
|
name: The name identifier for this integration
|
17
22
|
store: Store instance for persisting credentials and other data
|
18
23
|
"""
|
24
|
+
|
19
25
|
def __init__(self, name: str, store: Store = None):
|
20
26
|
self.name = name
|
21
27
|
self.store = store
|
@@ -23,19 +29,19 @@ class Integration(ABC):
|
|
23
29
|
@abstractmethod
|
24
30
|
def authorize(self):
|
25
31
|
"""Authorize the integration.
|
26
|
-
|
32
|
+
|
27
33
|
Returns:
|
28
34
|
str: Authorization URL.
|
29
35
|
"""
|
30
36
|
pass
|
31
|
-
|
37
|
+
|
32
38
|
@abstractmethod
|
33
39
|
def get_credentials(self):
|
34
40
|
"""Get credentials for the integration.
|
35
|
-
|
41
|
+
|
36
42
|
Returns:
|
37
43
|
dict: Credentials for the integration.
|
38
|
-
|
44
|
+
|
39
45
|
Raises:
|
40
46
|
NotAuthorizedError: If credentials are not found.
|
41
47
|
"""
|
@@ -44,31 +50,62 @@ class Integration(ABC):
|
|
44
50
|
@abstractmethod
|
45
51
|
def set_credentials(self, credentials: dict):
|
46
52
|
"""Set credentials for the integration.
|
47
|
-
|
53
|
+
|
48
54
|
Args:
|
49
55
|
credentials: Credentials for the integration.
|
50
56
|
"""
|
51
57
|
pass
|
52
58
|
|
53
59
|
|
54
|
-
|
55
60
|
class ApiKeyIntegration(Integration):
|
61
|
+
"""Integration class for API key based authentication.
|
62
|
+
|
63
|
+
This class implements the Integration interface for services that use API key
|
64
|
+
authentication. It handles storing and retrieving API keys using the provided
|
65
|
+
store.
|
66
|
+
|
67
|
+
Args:
|
68
|
+
name: The name identifier for this integration
|
69
|
+
store: Optional Store instance for persisting credentials and other data
|
70
|
+
**kwargs: Additional keyword arguments passed to parent class
|
71
|
+
|
72
|
+
Attributes:
|
73
|
+
name: The name identifier for this integration
|
74
|
+
store: Store instance for persisting credentials and other data
|
75
|
+
"""
|
76
|
+
|
56
77
|
def __init__(self, name: str, store: Store = None, **kwargs):
|
57
78
|
super().__init__(name, store, **kwargs)
|
79
|
+
if not name.endswith("api_key"):
|
80
|
+
self.name = f"{name}_api_key"
|
81
|
+
logger.info(f"Initializing API Key Integration: {name} with store: {store}")
|
58
82
|
|
59
83
|
def get_credentials(self):
|
60
84
|
credentials = self.store.get(self.name)
|
85
|
+
if credentials is None:
|
86
|
+
action = self.authorize()
|
87
|
+
raise NotAuthorizedError(action)
|
61
88
|
return credentials
|
62
89
|
|
63
90
|
def set_credentials(self, credentials: dict):
|
64
91
|
self.store.set(self.name, credentials)
|
65
92
|
|
66
93
|
def authorize(self):
|
67
|
-
return
|
68
|
-
|
94
|
+
return f"Please ask the user for api key and set the API Key for {self.name} in the store"
|
95
|
+
|
69
96
|
|
70
97
|
class OAuthIntegration(Integration):
|
71
|
-
def __init__(
|
98
|
+
def __init__(
|
99
|
+
self,
|
100
|
+
name: str,
|
101
|
+
store: Store = None,
|
102
|
+
client_id: str = None,
|
103
|
+
client_secret: str = None,
|
104
|
+
auth_url: str = None,
|
105
|
+
token_url: str = None,
|
106
|
+
scope: str = None,
|
107
|
+
**kwargs,
|
108
|
+
):
|
72
109
|
super().__init__(name, store, **kwargs)
|
73
110
|
self.client_id = client_id
|
74
111
|
self.client_secret = client_secret
|
@@ -81,42 +118,42 @@ class OAuthIntegration(Integration):
|
|
81
118
|
if not credentials:
|
82
119
|
return None
|
83
120
|
return credentials
|
84
|
-
|
121
|
+
|
85
122
|
def set_credentials(self, credentials: dict):
|
86
123
|
if not credentials or not isinstance(credentials, dict):
|
87
124
|
raise ValueError("Invalid credentials format")
|
88
|
-
if
|
125
|
+
if "access_token" not in credentials:
|
89
126
|
raise ValueError("Credentials must contain access_token")
|
90
127
|
self.store.set(self.name, credentials)
|
91
128
|
|
92
129
|
def authorize(self):
|
93
130
|
if not all([self.client_id, self.client_secret, self.auth_url, self.token_url]):
|
94
131
|
raise ValueError("Missing required OAuth configuration")
|
95
|
-
|
132
|
+
|
96
133
|
auth_params = {
|
97
|
-
|
98
|
-
|
99
|
-
|
134
|
+
"client_id": self.client_id,
|
135
|
+
"response_type": "code",
|
136
|
+
"scope": self.scope,
|
100
137
|
}
|
101
|
-
|
138
|
+
|
102
139
|
return {
|
103
140
|
"url": self.auth_url,
|
104
141
|
"params": auth_params,
|
105
142
|
"client_secret": self.client_secret,
|
106
|
-
"token_url": self.token_url
|
143
|
+
"token_url": self.token_url,
|
107
144
|
}
|
108
|
-
|
145
|
+
|
109
146
|
def handle_callback(self, code: str):
|
110
147
|
if not all([self.client_id, self.client_secret, self.token_url]):
|
111
148
|
raise ValueError("Missing required OAuth configuration")
|
112
|
-
|
149
|
+
|
113
150
|
token_params = {
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
151
|
+
"client_id": self.client_id,
|
152
|
+
"client_secret": self.client_secret,
|
153
|
+
"code": code,
|
154
|
+
"grant_type": "authorization_code",
|
118
155
|
}
|
119
|
-
|
156
|
+
|
120
157
|
response = httpx.post(self.token_url, data=token_params)
|
121
158
|
response.raise_for_status()
|
122
159
|
credentials = response.json()
|
@@ -126,16 +163,16 @@ class OAuthIntegration(Integration):
|
|
126
163
|
def refresh_token(self):
|
127
164
|
if not all([self.client_id, self.client_secret, self.token_url]):
|
128
165
|
raise ValueError("Missing required OAuth configuration")
|
129
|
-
|
166
|
+
|
130
167
|
token_params = {
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
168
|
+
"client_id": self.client_id,
|
169
|
+
"client_secret": self.client_secret,
|
170
|
+
"grant_type": "refresh_token",
|
171
|
+
"refresh_token": self.credentials["refresh_token"],
|
135
172
|
}
|
136
|
-
|
173
|
+
|
137
174
|
response = httpx.post(self.token_url, data=token_params)
|
138
175
|
response.raise_for_status()
|
139
176
|
credentials = response.json()
|
140
177
|
self.store.set(self.name, credentials)
|
141
|
-
return credentials
|
178
|
+
return credentials
|
@@ -1,3 +1,23 @@
|
|
1
|
+
from universal_mcp.config import ServerConfig
|
1
2
|
from universal_mcp.servers.server import AgentRServer, LocalServer
|
2
3
|
|
3
|
-
|
4
|
+
|
5
|
+
def server_from_config(config: ServerConfig):
|
6
|
+
if config.type == "agentr":
|
7
|
+
return AgentRServer(
|
8
|
+
name=config.name,
|
9
|
+
description=config.description,
|
10
|
+
api_key=config.api_key,
|
11
|
+
port=config.port,
|
12
|
+
)
|
13
|
+
elif config.type == "local":
|
14
|
+
return LocalServer(
|
15
|
+
name=config.name,
|
16
|
+
description=config.description,
|
17
|
+
store=config.store,
|
18
|
+
apps_list=config.apps,
|
19
|
+
port=config.port,
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
__all__ = [AgentRServer, LocalServer]
|