bizteamai-smcp-biz 1.13.1__tar.gz

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.
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: bizteamai-smcp-biz
3
+ Version: 1.13.1
4
+ Summary: SMCP Business Edition - Secure Model Context Protocol with advanced licensing
5
+ Author-email: BizTeam AI <support@bizteamai.com>
6
+ License: Commercial
7
+ Project-URL: Homepage, https://github.com/bizteamai
8
+ Project-URL: Documentation, https://github.com/bizteamai/smcp-biz
9
+ Project-URL: Repository, https://github.com/bizteamai/smcp-biz
10
+ Project-URL: Bug Tracker, https://github.com/bizteamai/smcp-biz/issues
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: mcp
21
+ Requires-Dist: fastmcp
22
+ Requires-Dist: cryptography
23
+ Requires-Dist: pyyaml
24
+ Requires-Dist: psutil>=5.8.0
25
+ Requires-Dist: requests>=2.25.0
26
+ Provides-Extra: support
27
+ Requires-Dist: psutil>=5.8.0; extra == "support"
28
+ Requires-Dist: requests>=2.25.0; extra == "support"
29
+
30
+ # SMCP Business Edition
31
+
32
+ Professional secure MCP server implementation with advanced licensing and enterprise features.
33
+
34
+ ## Key Features
35
+
36
+ - **Core-based Licensing**: License validation based on CPU core usage
37
+ - **Runtime Enforcement**: Graceful enforcement with 15-minute grace period
38
+ - **Automatic Renewal**: Hot-reload license keys without restarts
39
+ - **Revocation Support**: Remote revocation list checking
40
+ - **Enterprise Security**: All community features plus advanced compliance
41
+ - **Professional Support**: Priority support and SLA guarantees
42
+ - **Seamless Upgrade**: Same import (`import smcp`) as community edition
43
+
44
+ ## Installation
45
+
46
+ ### Business Edition
47
+ #### From Private PyPI Server
48
+ ```bash
49
+ pip install --extra-index-url https://bizteamai.com/pypi/simple/ smcp-biz
50
+ ```
51
+
52
+ #### With Upload Authentication (for contributors)
53
+ ```bash
54
+ # For uploading packages (requires token authentication)
55
+ pip install --extra-index-url https://<upload-token>@bizteamai.com/pypi/simple/ bizteam-smcp-biz
56
+ ```
57
+
58
+ **Note**:
59
+ - Package name is `smcp-biz` but imports as `smcp` for seamless upgrade from community edition
60
+ - Public downloads don't require authentication
61
+ - Upload operations require a valid token
62
+ - Private PyPI server hosted at `https://bizteamai.com/pypi/`
63
+
64
+ ### Community Edition
65
+
66
+ The community edition (`bizteam-smcp`) is available on PyPI.org:
67
+ ```bash
68
+ # Community edition from PyPI.org
69
+ pip install bizteam-smcp
70
+
71
+ # Or from private PyPI
72
+ pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp
73
+ ```
74
+
75
+ ## License Configuration
76
+
77
+ Set your license file path:
78
+ ```bash
79
+ export BIZTEAM_LICENSE_FILE=/etc/bizteam/license.txt
80
+ ```
81
+
82
+ Or set the license key directly:
83
+ ```bash
84
+ export BIZTEAM_LICENSE_KEY="BZT.customer.cores.expiry.nonce.signature"
85
+ ```
86
+
87
+ ## Development Mode
88
+
89
+ For development and testing:
90
+ ```bash
91
+ export BIZTEAM_DEV_MODE=1 # Disables license checking
92
+ ```
93
+
94
+ ## Usage
95
+
96
+ ```python
97
+ import smcp # Same import as community edition
98
+ # Business edition will be used if installed
99
+ ```
100
+
101
+ ## CLI Tools
102
+
103
+ Enhanced CLI tools included:
104
+ ```bash
105
+ smcp-gen-key # Generate license keys
106
+ smcp-approve # Approve pending actions
107
+ smcp-mkcert # Certificate generation
108
+ smcp-revoke # License revocation
109
+ ```
110
+
111
+ ## Support
112
+
113
+ - **Technical Support**: support@bizteamai.com
114
+ - **Sales Inquiries**: sales@bizteamai.com
115
+ - **GitHub**: https://github.com/bizteamai/smcp-biz
116
+
117
+ ## License
118
+
119
+ This is commercial software distributed under a proprietary license. A valid license key is required for production use.
@@ -0,0 +1,90 @@
1
+ # SMCP Business Edition
2
+
3
+ Professional secure MCP server implementation with advanced licensing and enterprise features.
4
+
5
+ ## Key Features
6
+
7
+ - **Core-based Licensing**: License validation based on CPU core usage
8
+ - **Runtime Enforcement**: Graceful enforcement with 15-minute grace period
9
+ - **Automatic Renewal**: Hot-reload license keys without restarts
10
+ - **Revocation Support**: Remote revocation list checking
11
+ - **Enterprise Security**: All community features plus advanced compliance
12
+ - **Professional Support**: Priority support and SLA guarantees
13
+ - **Seamless Upgrade**: Same import (`import smcp`) as community edition
14
+
15
+ ## Installation
16
+
17
+ ### Business Edition
18
+ #### From Private PyPI Server
19
+ ```bash
20
+ pip install --extra-index-url https://bizteamai.com/pypi/simple/ smcp-biz
21
+ ```
22
+
23
+ #### With Upload Authentication (for contributors)
24
+ ```bash
25
+ # For uploading packages (requires token authentication)
26
+ pip install --extra-index-url https://<upload-token>@bizteamai.com/pypi/simple/ bizteam-smcp-biz
27
+ ```
28
+
29
+ **Note**:
30
+ - Package name is `smcp-biz` but imports as `smcp` for seamless upgrade from community edition
31
+ - Public downloads don't require authentication
32
+ - Upload operations require a valid token
33
+ - Private PyPI server hosted at `https://bizteamai.com/pypi/`
34
+
35
+ ### Community Edition
36
+
37
+ The community edition (`bizteam-smcp`) is available on PyPI.org:
38
+ ```bash
39
+ # Community edition from PyPI.org
40
+ pip install bizteam-smcp
41
+
42
+ # Or from private PyPI
43
+ pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp
44
+ ```
45
+
46
+ ## License Configuration
47
+
48
+ Set your license file path:
49
+ ```bash
50
+ export BIZTEAM_LICENSE_FILE=/etc/bizteam/license.txt
51
+ ```
52
+
53
+ Or set the license key directly:
54
+ ```bash
55
+ export BIZTEAM_LICENSE_KEY="BZT.customer.cores.expiry.nonce.signature"
56
+ ```
57
+
58
+ ## Development Mode
59
+
60
+ For development and testing:
61
+ ```bash
62
+ export BIZTEAM_DEV_MODE=1 # Disables license checking
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ```python
68
+ import smcp # Same import as community edition
69
+ # Business edition will be used if installed
70
+ ```
71
+
72
+ ## CLI Tools
73
+
74
+ Enhanced CLI tools included:
75
+ ```bash
76
+ smcp-gen-key # Generate license keys
77
+ smcp-approve # Approve pending actions
78
+ smcp-mkcert # Certificate generation
79
+ smcp-revoke # License revocation
80
+ ```
81
+
82
+ ## Support
83
+
84
+ - **Technical Support**: support@bizteamai.com
85
+ - **Sales Inquiries**: sales@bizteamai.com
86
+ - **GitHub**: https://github.com/bizteamai/smcp-biz
87
+
88
+ ## License
89
+
90
+ This is commercial software distributed under a proprietary license. A valid license key is required for production use.
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: bizteamai-smcp-biz
3
+ Version: 1.13.1
4
+ Summary: SMCP Business Edition - Secure Model Context Protocol with advanced licensing
5
+ Author-email: BizTeam AI <support@bizteamai.com>
6
+ License: Commercial
7
+ Project-URL: Homepage, https://github.com/bizteamai
8
+ Project-URL: Documentation, https://github.com/bizteamai/smcp-biz
9
+ Project-URL: Repository, https://github.com/bizteamai/smcp-biz
10
+ Project-URL: Bug Tracker, https://github.com/bizteamai/smcp-biz/issues
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: Other/Proprietary License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: mcp
21
+ Requires-Dist: fastmcp
22
+ Requires-Dist: cryptography
23
+ Requires-Dist: pyyaml
24
+ Requires-Dist: psutil>=5.8.0
25
+ Requires-Dist: requests>=2.25.0
26
+ Provides-Extra: support
27
+ Requires-Dist: psutil>=5.8.0; extra == "support"
28
+ Requires-Dist: requests>=2.25.0; extra == "support"
29
+
30
+ # SMCP Business Edition
31
+
32
+ Professional secure MCP server implementation with advanced licensing and enterprise features.
33
+
34
+ ## Key Features
35
+
36
+ - **Core-based Licensing**: License validation based on CPU core usage
37
+ - **Runtime Enforcement**: Graceful enforcement with 15-minute grace period
38
+ - **Automatic Renewal**: Hot-reload license keys without restarts
39
+ - **Revocation Support**: Remote revocation list checking
40
+ - **Enterprise Security**: All community features plus advanced compliance
41
+ - **Professional Support**: Priority support and SLA guarantees
42
+ - **Seamless Upgrade**: Same import (`import smcp`) as community edition
43
+
44
+ ## Installation
45
+
46
+ ### Business Edition
47
+ #### From Private PyPI Server
48
+ ```bash
49
+ pip install --extra-index-url https://bizteamai.com/pypi/simple/ smcp-biz
50
+ ```
51
+
52
+ #### With Upload Authentication (for contributors)
53
+ ```bash
54
+ # For uploading packages (requires token authentication)
55
+ pip install --extra-index-url https://<upload-token>@bizteamai.com/pypi/simple/ bizteam-smcp-biz
56
+ ```
57
+
58
+ **Note**:
59
+ - Package name is `smcp-biz` but imports as `smcp` for seamless upgrade from community edition
60
+ - Public downloads don't require authentication
61
+ - Upload operations require a valid token
62
+ - Private PyPI server hosted at `https://bizteamai.com/pypi/`
63
+
64
+ ### Community Edition
65
+
66
+ The community edition (`bizteam-smcp`) is available on PyPI.org:
67
+ ```bash
68
+ # Community edition from PyPI.org
69
+ pip install bizteam-smcp
70
+
71
+ # Or from private PyPI
72
+ pip install --extra-index-url https://bizteamai.com/pypi/simple/ bizteam-smcp
73
+ ```
74
+
75
+ ## License Configuration
76
+
77
+ Set your license file path:
78
+ ```bash
79
+ export BIZTEAM_LICENSE_FILE=/etc/bizteam/license.txt
80
+ ```
81
+
82
+ Or set the license key directly:
83
+ ```bash
84
+ export BIZTEAM_LICENSE_KEY="BZT.customer.cores.expiry.nonce.signature"
85
+ ```
86
+
87
+ ## Development Mode
88
+
89
+ For development and testing:
90
+ ```bash
91
+ export BIZTEAM_DEV_MODE=1 # Disables license checking
92
+ ```
93
+
94
+ ## Usage
95
+
96
+ ```python
97
+ import smcp # Same import as community edition
98
+ # Business edition will be used if installed
99
+ ```
100
+
101
+ ## CLI Tools
102
+
103
+ Enhanced CLI tools included:
104
+ ```bash
105
+ smcp-gen-key # Generate license keys
106
+ smcp-approve # Approve pending actions
107
+ smcp-mkcert # Certificate generation
108
+ smcp-revoke # License revocation
109
+ ```
110
+
111
+ ## Support
112
+
113
+ - **Technical Support**: support@bizteamai.com
114
+ - **Sales Inquiries**: sales@bizteamai.com
115
+ - **GitHub**: https://github.com/bizteamai/smcp-biz
116
+
117
+ ## License
118
+
119
+ This is commercial software distributed under a proprietary license. A valid license key is required for production use.
@@ -0,0 +1,24 @@
1
+ README.md
2
+ pyproject.toml
3
+ bizteamai_smcp_biz.egg-info/PKG-INFO
4
+ bizteamai_smcp_biz.egg-info/SOURCES.txt
5
+ bizteamai_smcp_biz.egg-info/dependency_links.txt
6
+ bizteamai_smcp_biz.egg-info/entry_points.txt
7
+ bizteamai_smcp_biz.egg-info/requires.txt
8
+ bizteamai_smcp_biz.egg-info/top_level.txt
9
+ smcp/__init__.py
10
+ smcp/allowlist.py
11
+ smcp/app_wrapper.py
12
+ smcp/confirm.py
13
+ smcp/cpu.py
14
+ smcp/decorators.py
15
+ smcp/enforce.py
16
+ smcp/filters.py
17
+ smcp/license.py
18
+ smcp/logchain.py
19
+ smcp/tls.py
20
+ smcp/cli/__init__.py
21
+ smcp/cli/approve.py
22
+ smcp/cli/gen_key.py
23
+ smcp/cli/mkcert.py
24
+ smcp/cli/revoke.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ smcp-approve = smcp.cli.approve:main
3
+ smcp-gen-key = smcp.cli.gen_key:main
4
+ smcp-mkcert = smcp.cli.mkcert:main
5
+ smcp-revoke = smcp.cli.revoke:main
@@ -0,0 +1,10 @@
1
+ mcp
2
+ fastmcp
3
+ cryptography
4
+ pyyaml
5
+ psutil>=5.8.0
6
+ requests>=2.25.0
7
+
8
+ [support]
9
+ psutil>=5.8.0
10
+ requests>=2.25.0
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "bizteamai-smcp-biz"
7
+ version = "1.13.1"
8
+ description = "SMCP Business Edition - Secure Model Context Protocol with advanced licensing"
9
+ authors = [
10
+ {name = "BizTeam AI", email = "support@bizteamai.com"}
11
+ ]
12
+ readme = "README.md"
13
+ license = {text = "Commercial"}
14
+ requires-python = ">=3.10"
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: Other/Proprietary License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ ]
24
+ dependencies = [
25
+ "mcp",
26
+ "fastmcp",
27
+ "cryptography",
28
+ "pyyaml",
29
+ "psutil>=5.8.0",
30
+ "requests>=2.25.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ support = [
35
+ "psutil>=5.8.0",
36
+ "requests>=2.25.0",
37
+ ]
38
+
39
+ [project.scripts]
40
+ smcp-mkcert = "smcp.cli.mkcert:main"
41
+ smcp-approve = "smcp.cli.approve:main"
42
+ smcp-gen-key = "smcp.cli.gen_key:main"
43
+ smcp-revoke = "smcp.cli.revoke:main"
44
+
45
+ [project.urls]
46
+ Homepage = "https://github.com/bizteamai"
47
+ Documentation = "https://github.com/bizteamai/smcp-biz"
48
+ Repository = "https://github.com/bizteamai/smcp-biz"
49
+ "Bug Tracker" = "https://github.com/bizteamai/smcp-biz/issues"
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["."]
53
+ include = ["smcp*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,53 @@
1
+ """
2
+ SMCP Business Edition - Secure Model Context Protocol
3
+
4
+ Professional secure MCP server implementation with core-based licensing.
5
+ """
6
+
7
+ import logging
8
+ import atexit
9
+ import os
10
+
11
+ from .license import verify_license, get_licensed_cores
12
+ from .enforce import start_enforcement
13
+
14
+ # Import core SMCP functionality
15
+ from .app_wrapper import FastSMCP
16
+ from .decorators import tool, prompt, retrieval
17
+
18
+ __version__ = "0.1.0"
19
+ __all__ = ["FastSMCP", "tool", "prompt", "retrieval"]
20
+
21
+ # Initialize licensing system
22
+ logger = logging.getLogger(__name__)
23
+
24
+ def _initialize_licensing():
25
+ """Initialize the licensing system on module import."""
26
+ try:
27
+ # Check if we're in development/testing mode
28
+ if os.getenv('BIZTEAM_DEV_MODE') == '1':
29
+ logger.info("SMCP Business Edition - Development mode (license checking disabled)")
30
+ return
31
+
32
+ # Verify license on import
33
+ license_info = verify_license()
34
+ if license_info:
35
+ licensed_cores = get_licensed_cores()
36
+ logger.info(f"SMCP Business Edition - Licensed for {licensed_cores} cores")
37
+
38
+ # Start enforcement monitoring
39
+ start_enforcement()
40
+ else:
41
+ logger.error("SMCP Business Edition - Invalid or missing license")
42
+ raise RuntimeError("Valid license required for SMCP Business Edition")
43
+
44
+ except Exception as e:
45
+ logger.error(f"License initialization failed: {e}")
46
+ if os.getenv('BIZTEAM_DEV_MODE') != '1':
47
+ raise
48
+
49
+ # Initialize on import
50
+ _initialize_licensing()
51
+
52
+ # Clean up on exit
53
+ atexit.register(lambda: logger.debug("SMCP Business Edition - Shutting down"))
@@ -0,0 +1,169 @@
1
+ """
2
+ Host allowlist validation for outbound connections.
3
+ """
4
+
5
+ import ipaddress
6
+ import re
7
+ from typing import Dict, List, Union
8
+ from urllib.parse import urlparse
9
+
10
+
11
+ class HostValidationError(Exception):
12
+ """Raised when a host fails allowlist validation."""
13
+ pass
14
+
15
+
16
+ def validate_host(target: str, cfg: Dict[str, Union[str, List[str]]]) -> None:
17
+ """
18
+ Validate that a target host is in the allowlist.
19
+
20
+ Args:
21
+ target: Target host, URL, or IP address to validate
22
+ cfg: Configuration dictionary containing ALLOWED_HOSTS
23
+
24
+ Raises:
25
+ HostValidationError: If the host is not in the allowlist
26
+ """
27
+ allowed_hosts = cfg.get("ALLOWED_HOSTS", [])
28
+ if not allowed_hosts:
29
+ return # No allowlist configured, allow all
30
+
31
+ # Extract hostname from URL if needed
32
+ hostname = _extract_hostname(target)
33
+
34
+ # Check against allowlist
35
+ if not _is_host_allowed(hostname, allowed_hosts):
36
+ raise HostValidationError(f"Host '{hostname}' not in allowlist")
37
+
38
+
39
+ def _extract_hostname(target: str) -> str:
40
+ """
41
+ Extract hostname from a target string (URL, hostname, or IP).
42
+
43
+ Args:
44
+ target: Target string to parse
45
+
46
+ Returns:
47
+ Extracted hostname or IP address
48
+ """
49
+ # If it looks like a URL, parse it
50
+ if "://" in target:
51
+ parsed = urlparse(target)
52
+ return parsed.hostname or parsed.netloc
53
+
54
+ # If it contains a port, strip it
55
+ if ":" in target and not _is_ipv6(target):
56
+ return target.split(":")[0]
57
+
58
+ return target
59
+
60
+
61
+ def _is_ipv6(address: str) -> bool:
62
+ """Check if a string is an IPv6 address."""
63
+ try:
64
+ ipaddress.IPv6Address(address)
65
+ return True
66
+ except ipaddress.AddressValueError:
67
+ return False
68
+
69
+
70
+ def _is_host_allowed(hostname: str, allowed_hosts: List[str]) -> bool:
71
+ """
72
+ Check if a hostname is in the allowlist.
73
+
74
+ Args:
75
+ hostname: Hostname to check
76
+ allowed_hosts: List of allowed hosts (can include patterns)
77
+
78
+ Returns:
79
+ True if the hostname is allowed
80
+ """
81
+ for allowed in allowed_hosts:
82
+ if _host_matches(hostname, allowed):
83
+ return True
84
+ return False
85
+
86
+
87
+ def _host_matches(hostname: str, pattern: str) -> bool:
88
+ """
89
+ Check if a hostname matches an allowlist pattern.
90
+
91
+ Supports:
92
+ - Exact matches: "api.example.com"
93
+ - Wildcard subdomains: "*.example.com"
94
+ - IP addresses: "192.168.1.1"
95
+ - IP ranges: "192.168.1.0/24"
96
+
97
+ Args:
98
+ hostname: Hostname to check
99
+ pattern: Pattern to match against
100
+
101
+ Returns:
102
+ True if the hostname matches the pattern
103
+ """
104
+ # Exact match
105
+ if hostname == pattern:
106
+ return True
107
+
108
+ # Wildcard subdomain match
109
+ if pattern.startswith("*."):
110
+ domain = pattern[2:]
111
+ return hostname.endswith(f".{domain}") or hostname == domain
112
+
113
+ # IP range match
114
+ if "/" in pattern:
115
+ try:
116
+ network = ipaddress.ip_network(pattern, strict=False)
117
+ address = ipaddress.ip_address(hostname)
118
+ return address in network
119
+ except (ipaddress.AddressValueError, ValueError):
120
+ pass
121
+
122
+ # Regex pattern match (if pattern contains regex characters)
123
+ if any(char in pattern for char in r"[](){}+?^$|\\"):
124
+ try:
125
+ return bool(re.match(pattern, hostname))
126
+ except re.error:
127
+ pass
128
+
129
+ return False
130
+
131
+
132
+ def add_host_to_allowlist(cfg: Dict[str, List[str]], host: str) -> None:
133
+ """
134
+ Add a host to the allowlist configuration.
135
+
136
+ Args:
137
+ cfg: Configuration dictionary to modify
138
+ host: Host to add to the allowlist
139
+ """
140
+ if "ALLOWED_HOSTS" not in cfg:
141
+ cfg["ALLOWED_HOSTS"] = []
142
+
143
+ if host not in cfg["ALLOWED_HOSTS"]:
144
+ cfg["ALLOWED_HOSTS"].append(host)
145
+
146
+
147
+ def remove_host_from_allowlist(cfg: Dict[str, List[str]], host: str) -> None:
148
+ """
149
+ Remove a host from the allowlist configuration.
150
+
151
+ Args:
152
+ cfg: Configuration dictionary to modify
153
+ host: Host to remove from the allowlist
154
+ """
155
+ if "ALLOWED_HOSTS" in cfg and host in cfg["ALLOWED_HOSTS"]:
156
+ cfg["ALLOWED_HOSTS"].remove(host)
157
+
158
+
159
+ def get_allowed_hosts(cfg: Dict[str, List[str]]) -> List[str]:
160
+ """
161
+ Get the current allowlist.
162
+
163
+ Args:
164
+ cfg: Configuration dictionary
165
+
166
+ Returns:
167
+ List of allowed hosts
168
+ """
169
+ return cfg.get("ALLOWED_HOSTS", [])