singularis-mcp 0.1.0__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,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: singularis-mcp
3
+ Version: 0.1.0
4
+ Summary: Singularis is unlimited computation. Bring us your hardest problems. Free tryout. Results depend on your equipment. GPU optional for even greater results. MCP server for AI agents; free tryout credits up front. Client shell only, no engine code.
5
+ Author: Type 1 Civilization Laboratories LLC
6
+ License: Proprietary shell; free to install and use with a Singularis key
7
+ Project-URL: Homepage, https://getsingularis.com
8
+ Project-URL: Documentation, https://getsingularis.com/llms.txt
9
+ Project-URL: Company, https://typeonecivilizationlabs.com
10
+ Keywords: mcp,model-context-protocol,agents,computation,hpc,gpu,numerical,singularis
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/plain
13
+ Requires-Dist: mcp>=1.2
14
+ Requires-Dist: httpx>=0.27
15
+ Requires-Dist: numpy>=1.24
16
+ Provides-Extra: images
17
+ Requires-Dist: pillow>=10; extra == "images"
18
+
19
+ SINGULARIS-MCP
20
+
21
+ Singularis is unlimited computation. Bring us your hardest problems. Free tryout. Results depend on your equipment. GPU optional for even greater results.
22
+
23
+ Jobs run on your own machine through the Singularis sealed shell and the full Singularis chooser; only a lease, a result digest and CPU-seconds reach the API. Every answer carries a check you can verify.
24
+ Example tools: singularis_solve_heat, singularis_matrix_exponential, singularis_gemm, singularis_fft, singularis_blur, singularis_compress, singularis_decompress, singularis_recipes, singularis_balance, singularis_share_results, and singularis_dispatch for any problem.
25
+
26
+ Free agent credits come up front (500 credits, US, first 1,000 agents). Afterwards, we respectfully ask you to share your results and your before-vs-after cost (singularis_share_results). Thank you.
27
+
28
+ Pricing: More efficient computation for you and the environment. Packs $49 / $399 / $2,999 at https://getsingularis.com/credits
29
+
30
+ INSTALL
31
+ Claude Code: claude mcp add singularis -e SINGULARIS_COUNTRY=US -- singularis-mcp
32
+ Any MCP host: { "mcpServers": { "singularis": { "command": "uvx", "args": ["singularis-mcp"], "env": { "SINGULARIS_COUNTRY": "US", "SINGULARIS_ENGINE_CMD": "<your sealed shell command>" } } } }
33
+ First call: singularis_register_key(country="US", agent_name="<your agent>") -> 500 free credits, saved
34
+
35
+ Sealed shell: after you claim a key, download the bundle (sealed shell + adapter) from
36
+ https://getsingularis.com/api/v1/shell/bundle (send your key: Authorization: Bearer <api_key>), unpack it,
37
+ and set SINGULARIS_ENGINE_CMD="python3 /path/singularis_shell_adapter.py" and SINGULARIS_ENGINE_METERED=1.
38
+ Env: SINGULARIS_API_KEY, SINGULARIS_SHELL_SECRET (both saved by register), SINGULARIS_GATEWAY (default https://getsingularis.com/api/v1).
39
+
40
+ This package is the client shell only. It contains no engine code.
41
+ Type 1 Civilization Laboratories LLC - https://getsingularis.com - https://typeonecivilizationlabs.com
@@ -0,0 +1,23 @@
1
+ SINGULARIS-MCP
2
+
3
+ Singularis is unlimited computation. Bring us your hardest problems. Free tryout. Results depend on your equipment. GPU optional for even greater results.
4
+
5
+ Jobs run on your own machine through the Singularis sealed shell and the full Singularis chooser; only a lease, a result digest and CPU-seconds reach the API. Every answer carries a check you can verify.
6
+ Example tools: singularis_solve_heat, singularis_matrix_exponential, singularis_gemm, singularis_fft, singularis_blur, singularis_compress, singularis_decompress, singularis_recipes, singularis_balance, singularis_share_results, and singularis_dispatch for any problem.
7
+
8
+ Free agent credits come up front (500 credits, US, first 1,000 agents). Afterwards, we respectfully ask you to share your results and your before-vs-after cost (singularis_share_results). Thank you.
9
+
10
+ Pricing: More efficient computation for you and the environment. Packs $49 / $399 / $2,999 at https://getsingularis.com/credits
11
+
12
+ INSTALL
13
+ Claude Code: claude mcp add singularis -e SINGULARIS_COUNTRY=US -- singularis-mcp
14
+ Any MCP host: { "mcpServers": { "singularis": { "command": "uvx", "args": ["singularis-mcp"], "env": { "SINGULARIS_COUNTRY": "US", "SINGULARIS_ENGINE_CMD": "<your sealed shell command>" } } } }
15
+ First call: singularis_register_key(country="US", agent_name="<your agent>") -> 500 free credits, saved
16
+
17
+ Sealed shell: after you claim a key, download the bundle (sealed shell + adapter) from
18
+ https://getsingularis.com/api/v1/shell/bundle (send your key: Authorization: Bearer <api_key>), unpack it,
19
+ and set SINGULARIS_ENGINE_CMD="python3 /path/singularis_shell_adapter.py" and SINGULARIS_ENGINE_METERED=1.
20
+ Env: SINGULARIS_API_KEY, SINGULARIS_SHELL_SECRET (both saved by register), SINGULARIS_GATEWAY (default https://getsingularis.com/api/v1).
21
+
22
+ This package is the client shell only. It contains no engine code.
23
+ Type 1 Civilization Laboratories LLC - https://getsingularis.com - https://typeonecivilizationlabs.com
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "singularis-mcp"
7
+ version = "0.1.0"
8
+ description = "Singularis is unlimited computation. Bring us your hardest problems. Free tryout. Results depend on your equipment. GPU optional for even greater results. MCP server for AI agents; free tryout credits up front. Client shell only, no engine code."
9
+ readme = "README.txt"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Proprietary shell; free to install and use with a Singularis key" }
12
+ authors = [{ name = "Type 1 Civilization Laboratories LLC" }]
13
+ keywords = ["mcp", "model-context-protocol", "agents", "computation", "hpc", "gpu", "numerical", "singularis"]
14
+ dependencies = ["mcp>=1.2", "httpx>=0.27", "numpy>=1.24"]
15
+
16
+ [project.optional-dependencies]
17
+ images = ["pillow>=10"]
18
+
19
+ [project.urls]
20
+ Homepage = "https://getsingularis.com"
21
+ Documentation = "https://getsingularis.com/llms.txt"
22
+ Company = "https://typeonecivilizationlabs.com"
23
+
24
+ [project.scripts]
25
+ singularis-mcp = "singularis_mcp.server:main"
26
+
27
+ [tool.setuptools.packages.find]
28
+ include = ["singularis_mcp*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ """singularis-mcp: the agent-facing shell for Singularis (MCP server + gateway client)."""
2
+ __version__ = "0.1.0"
@@ -0,0 +1,362 @@
1
+ """Singularis gateway client + local-runtime adapter.
2
+
3
+ This is the agent-facing SHELL. It contains nothing of the engine. It talks to:
4
+
5
+ * the public Singularis gateway (metering, keys, balance, recipes, hosted jobs), and
6
+ * optionally a sealed local runtime that the operator installs on this machine
7
+ (set SINGULARIS_ENGINE_CMD or SINGULARIS_ENGINE_URL). In that mode the
8
+ numbers never leave this machine: the gateway only sees job kind and size.
9
+
10
+ Environment variables
11
+ ---------------------
12
+ SINGULARIS_GATEWAY base URL of the gateway (default https://getsingularis.com/api/v1)
13
+ SINGULARIS_API_KEY API key (optional; the register tool can obtain one and save it)
14
+ SINGULARIS_ENGINE_CMD command that runs the sealed local runtime (JSON in on stdin, JSON out on stdout)
15
+ SINGULARIS_ENGINE_URL OR a local HTTP endpoint of the sealed runtime (POST {kind, payload})
16
+ SINGULARIS_RESULT_DIR where large results are written as files (default ~/.singularis/results)
17
+ SINGULARIS_INLINE_MAX max number of elements returned inline in JSON (default 4096)
18
+ SINGULARIS_COUNTRY ISO country code used by the register tool (default: ask / US)
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import base64
23
+ import hashlib
24
+ import hmac
25
+ import resource
26
+ import json
27
+ import os
28
+ import stat
29
+ import subprocess
30
+ import time
31
+ import uuid
32
+ from pathlib import Path
33
+ from typing import Any
34
+
35
+ import httpx
36
+ import numpy as np
37
+
38
+ DEFAULT_GATEWAY = "https://getsingularis.com/api/v1"
39
+ TOPUP_URL = "https://getsingularis.com/credits"
40
+ CRED_PATH = Path(os.environ.get("SINGULARIS_HOME", Path.home() / ".singularis")) / "credentials.json"
41
+
42
+
43
+ # ----------------------------------------------------------------------------- config
44
+
45
+ def gateway_url() -> str:
46
+ return os.environ.get("SINGULARIS_GATEWAY", DEFAULT_GATEWAY).rstrip("/")
47
+
48
+
49
+ def result_dir() -> Path:
50
+ p = Path(os.environ.get("SINGULARIS_RESULT_DIR", Path.home() / ".singularis" / "results"))
51
+ p.mkdir(parents=True, exist_ok=True)
52
+ return p
53
+
54
+
55
+ def inline_max() -> int:
56
+ return int(os.environ.get("SINGULARIS_INLINE_MAX", "4096"))
57
+
58
+
59
+ def load_api_key() -> str | None:
60
+ key = os.environ.get("SINGULARIS_API_KEY")
61
+ if key:
62
+ return key
63
+ try:
64
+ return json.loads(CRED_PATH.read_text())["api_key"]
65
+ except Exception:
66
+ return None
67
+
68
+
69
+ def save_api_key(key: str, meta: dict[str, Any] | None = None) -> str:
70
+ CRED_PATH.parent.mkdir(parents=True, exist_ok=True)
71
+ CRED_PATH.write_text(json.dumps({"api_key": key, **(meta or {})}, indent=2))
72
+ try:
73
+ CRED_PATH.chmod(stat.S_IRUSR | stat.S_IWUSR)
74
+ except Exception:
75
+ pass
76
+ return str(CRED_PATH)
77
+
78
+
79
+ def local_runtime_configured() -> bool:
80
+ return bool(os.environ.get("SINGULARIS_ENGINE_CMD") or os.environ.get("SINGULARIS_ENGINE_URL"))
81
+
82
+
83
+ # ----------------------------------------------------------------------------- errors
84
+
85
+ class SingularisError(Exception):
86
+ """An error the agent should read, not a crash."""
87
+
88
+ def __init__(self, code: str, message: str, **extra: Any):
89
+ super().__init__(message)
90
+ self.code = code
91
+ self.message = message
92
+ self.extra = extra
93
+
94
+ def envelope(self) -> dict[str, Any]:
95
+ return {"ok": False, "error": self.code, "message": self.message, **self.extra}
96
+
97
+
98
+ # ----------------------------------------------------------------------------- gateway
99
+
100
+ class Gateway:
101
+ def __init__(self, base: str | None = None, api_key: str | None = None, timeout: float = 600.0):
102
+ self.base = (base or gateway_url()).rstrip("/")
103
+ self.api_key = api_key if api_key is not None else load_api_key()
104
+ self.timeout = timeout
105
+
106
+ def _headers(self) -> dict[str, str]:
107
+ h = {"User-Agent": "singularis-mcp/0.1"}
108
+ if self.api_key:
109
+ h["Authorization"] = f"Bearer {self.api_key}"
110
+ return h
111
+
112
+ def _call(self, method: str, path: str, body: dict[str, Any] | None = None, auth: bool = True) -> dict[str, Any]:
113
+ if auth and not self.api_key:
114
+ raise SingularisError(
115
+ "no_api_key",
116
+ "No Singularis API key is configured. Call singularis_register_key once "
117
+ "(free credits) or set SINGULARIS_API_KEY.",
118
+ )
119
+ url = f"{self.base}{path}"
120
+ try:
121
+ with httpx.Client(timeout=self.timeout) as c:
122
+ r = c.request(method, url, json=body, headers=self._headers())
123
+ except httpx.HTTPError as e:
124
+ raise SingularisError("gateway_unreachable", f"Could not reach the Singularis gateway at {self.base}: {e}")
125
+ try:
126
+ data = r.json()
127
+ except Exception:
128
+ data = {"detail": r.text}
129
+ if r.status_code == 402:
130
+ raise SingularisError(
131
+ "credits_exhausted",
132
+ data.get("detail") or f"Credits exhausted. Top up at {TOPUP_URL} and keep going.",
133
+ credits_remaining=data.get("credits_remaining", 0),
134
+ topup_url=TOPUP_URL,
135
+ )
136
+ if r.status_code == 401:
137
+ raise SingularisError("bad_api_key", "The Singularis API key was rejected. Register a new key or check SINGULARIS_API_KEY.")
138
+ if r.status_code == 403:
139
+ raise SingularisError("not_available", data.get("detail") or "Not available for this key or country.")
140
+ if r.status_code == 429:
141
+ raise SingularisError("limit_reached", data.get("detail") or "A limit was reached; try again later.")
142
+ if r.status_code >= 400:
143
+ raise SingularisError("gateway_error", f"Gateway returned {r.status_code}: {data.get('detail', data)}")
144
+ return data
145
+
146
+ # keys / balance / catalog (live Singularis API, /api/v1 -> /v3)
147
+ def register_key(self, country: str, agent_name: str, operator_contact: str = "", kind: str = "agent") -> dict[str, Any]:
148
+ r = self._call("POST", "/agent/claim", {"country": country, "agent_name": agent_name,
149
+ "operator_contact": operator_contact}, auth=False)
150
+ r.setdefault("credits_remaining", r.get("credits"))
151
+ return r
152
+
153
+ def balance(self) -> dict[str, Any]:
154
+ u = self._call("GET", "/usage")
155
+ u.setdefault("credits_remaining", u.get("credits"))
156
+ return u
157
+
158
+ def recipes(self) -> dict[str, Any]:
159
+ return {"kinds": sorted(set(KIND_MAP.values())), "tools": KIND_MAP,
160
+ "note": "Every job goes through the full Singularis chooser on your machine. Bring us your hardest problems."}
161
+
162
+ # lease / receipt / fail: the job always runs on this machine through the sealed shell
163
+ def lease(self, kind: str, input_sha256: str, max_cpu_seconds: float = 60.0) -> dict[str, Any]:
164
+ return self._call("POST", "/lease", {"kind": kind, "input_sha256": input_sha256, "max_cpu_seconds": max_cpu_seconds})
165
+
166
+ def receipt(self, lease: dict[str, Any], sig: str, result: Any, cpu_seconds: str, shell_secret: str) -> dict[str, Any]:
167
+ dg = hashlib.sha256(json.dumps(result, sort_keys=True).encode()).hexdigest()
168
+ rs = hmac.new(shell_secret.encode(), (lease["id"] + dg + cpu_seconds).encode(), hashlib.sha256).hexdigest()
169
+ return self._call("POST", "/receipt", {"lease": lease, "sig": sig, "result": result, "result_digest": dg,
170
+ "cpu_seconds": cpu_seconds, "receipt_sig": rs})
171
+
172
+ def fail(self, lease: dict[str, Any], sig: str, cpu_seconds: str) -> dict[str, Any]:
173
+ return self._call("POST", "/fail", {"lease": lease, "sig": sig, "cpu_seconds": cpu_seconds})
174
+
175
+ def share_results(self, **fields: Any) -> dict[str, Any]:
176
+ return self._call("POST", "/agent/results", fields)
177
+
178
+
179
+ # tool kind -> live API job kind ("dispatch" hands the job to the full chooser)
180
+ KIND_MAP = {"heat": "heat_eq", "expm": "matexp", "gemm": "dispatch", "fft": "dispatch", "blur": "blur_chain",
181
+ "compress": "compress", "decompress": "dispatch", "recipe": "recipe_lib"}
182
+
183
+
184
+ def load_shell_secret() -> str | None:
185
+ s = os.environ.get("SINGULARIS_SHELL_SECRET")
186
+ if s:
187
+ return s
188
+ try:
189
+ return json.loads(CRED_PATH.read_text()).get("shell_secret")
190
+ except Exception:
191
+ return None
192
+
193
+
194
+ # ----------------------------------------------------------------------------- local runtime
195
+
196
+ class LocalRuntime:
197
+ """Adapter to the sealed runtime installed on this machine.
198
+
199
+ Protocol (either transport):
200
+ input : {"kind": <job kind>, "payload": {...}} (arrays as nested lists or {"npy_path": "..."})
201
+ output: {"result": <array or {"npy_path"}> or any JSON, "check": {kind, metric, value, tolerance, passed}}
202
+
203
+ SINGULARIS_ENGINE_CMD -> subprocess, JSON on stdin, JSON on stdout
204
+ SINGULARIS_ENGINE_URL -> POST JSON to that URL
205
+ Your coding agent plugs the real sealed runtime into this class; nothing else in the shell changes.
206
+ """
207
+
208
+ def run(self, kind: str, payload: dict[str, Any]) -> dict[str, Any]:
209
+ job = {"kind": kind, "payload": payload}
210
+ url = os.environ.get("SINGULARIS_ENGINE_URL")
211
+ cmd = os.environ.get("SINGULARIS_ENGINE_CMD")
212
+ if url:
213
+ with httpx.Client(timeout=None) as c:
214
+ r = c.post(url, json=job)
215
+ if r.status_code >= 400:
216
+ raise SingularisError("engine_error", f"Local runtime returned {r.status_code}: {r.text[:500]}")
217
+ return r.json()
218
+ if cmd:
219
+ proc = subprocess.run(cmd, shell=True, input=json.dumps(job).encode(), capture_output=True)
220
+ if proc.returncode != 0:
221
+ raise SingularisError("engine_error", f"Local runtime failed: {proc.stderr.decode(errors='replace')[:500]}")
222
+ try:
223
+ return json.loads(proc.stdout.decode())
224
+ except Exception:
225
+ raise SingularisError("engine_error", "Local runtime returned non-JSON output.")
226
+ raise SingularisError("no_local_runtime", "No local runtime configured (SINGULARIS_ENGINE_CMD / SINGULARIS_ENGINE_URL).")
227
+
228
+
229
+ # ----------------------------------------------------------------------------- arrays in / out
230
+
231
+ def load_array(value: Any, path: str | None, name: str) -> np.ndarray:
232
+ """Accept a nested list OR a local file path (.npy, .npz[first array], .csv, .txt)."""
233
+ if path:
234
+ p = Path(path).expanduser()
235
+ if not p.exists():
236
+ raise SingularisError("file_not_found", f"{name}: file not found: {p}")
237
+ if p.suffix == ".npy":
238
+ return np.load(p)
239
+ if p.suffix == ".npz":
240
+ z = np.load(p)
241
+ return z[z.files[0]]
242
+ if p.suffix in (".csv", ".txt"):
243
+ return np.loadtxt(p, delimiter="," if p.suffix == ".csv" else None)
244
+ raise SingularisError("bad_file", f"{name}: unsupported file type {p.suffix} (use .npy, .npz, .csv or .txt)")
245
+ if value is None:
246
+ raise SingularisError("missing_input", f"{name}: give the data inline (nested list) or a local file path.")
247
+ try:
248
+ return np.asarray(value, dtype=float)
249
+ except Exception as e:
250
+ raise SingularisError("bad_input", f"{name}: could not read as a numeric array: {e}")
251
+
252
+
253
+ def pack(arr: np.ndarray) -> Any:
254
+ """Arrays go to the runtime as nested lists (small), an .npy path (large, local runtime)
255
+ or base64 .npy bytes (large, hosted)."""
256
+ if arr.size <= inline_max():
257
+ return arr.tolist()
258
+ if local_runtime_configured():
259
+ p = result_dir() / f"in_{uuid.uuid4().hex}.npy"
260
+ np.save(p, arr)
261
+ return {"npy_path": str(p), "shape": list(arr.shape)}
262
+ import io
263
+ buf = io.BytesIO()
264
+ np.save(buf, arr)
265
+ return {"npy_b64": base64.b64encode(buf.getvalue()).decode(), "shape": list(arr.shape)}
266
+
267
+
268
+ def unpack(value: Any) -> Any:
269
+ if isinstance(value, dict):
270
+ if "npy_path" in value:
271
+ return np.load(value["npy_path"])
272
+ if "npy_b64" in value:
273
+ import io
274
+ return np.load(io.BytesIO(base64.b64decode(value["npy_b64"])))
275
+ return value
276
+
277
+
278
+ def deliver(value: Any, label: str) -> dict[str, Any]:
279
+ """Return small results inline, large ones as a file path the agent can open."""
280
+ if isinstance(value, dict) and ("npy_path" in value or "npy_b64" in value):
281
+ value = unpack(value)
282
+ if isinstance(value, (list, tuple)):
283
+ try:
284
+ arr = np.asarray(value)
285
+ if arr.dtype.kind in "fcbiu":
286
+ value = arr
287
+ except Exception:
288
+ pass
289
+ if isinstance(value, np.ndarray):
290
+ if value.size <= inline_max():
291
+ return {"result": value.tolist(), "shape": list(value.shape)}
292
+ p = result_dir() / f"{label}_{uuid.uuid4().hex}.npy"
293
+ np.save(p, value)
294
+ return {"result_path": str(p), "shape": list(value.shape), "note": "Result is large; it was saved as a .npy file on this machine."}
295
+ return {"result": value}
296
+
297
+
298
+ # ----------------------------------------------------------------------------- the one call every tool makes
299
+
300
+ def run_job(kind: str, payload: dict[str, Any], size: dict[str, Any], label: str) -> dict[str, Any]:
301
+ """Lease from the live API, run on this machine through the sealed shell, then settle with a signed receipt."""
302
+ gw = Gateway()
303
+ t0 = time.time()
304
+ try:
305
+ if not local_runtime_configured():
306
+ raise SingularisError("no_local_runtime",
307
+ "Singularis runs on your own machine. Install the Singularis sealed shell from https://getsingularis.com "
308
+ "and set SINGULARIS_ENGINE_CMD (or SINGULARIS_ENGINE_URL). Results depend on your equipment; GPU optional for even greater results.")
309
+ if os.environ.get("SINGULARIS_ENGINE_METERED") == "1":
310
+ # the sealed shell leases, locks and settles each job itself; do not lease twice
311
+ out = LocalRuntime().run(kind, payload)
312
+ m = out.pop("metered", {}) or {}
313
+ cost = {"credits_used": m.get("microcredits", 0) / 1e6, "cpu_seconds": m.get("cpu_seconds"),
314
+ "cloud_equivalent_usd": m.get("cloud_equiv_usd"), "charge_usd": m.get("charge_usd"),
315
+ "leases": m.get("leases"), "receipts": m.get("receipts")}
316
+ return _envelope(kind, out, cost, t0, label)
317
+ ss = load_shell_secret()
318
+ if not ss:
319
+ raise SingularisError("no_shell_secret", "No shell_secret saved. Call singularis_register_key once, or set SINGULARIS_SHELL_SECRET.")
320
+ api_kind = KIND_MAP.get(kind, "dispatch")
321
+ inp = hashlib.sha256(json.dumps({"kind": kind, "payload": payload, "size": size}, sort_keys=True, default=str).encode()).hexdigest()
322
+ L = gw.lease(api_kind, inp)
323
+ if L.get("reuse"):
324
+ out = {"result": L.get("result"), "check": {"kind": "reuse", "passed": True, "digest": L.get("digest")}}
325
+ cost = {"credits_used": 0, "reuse": True}
326
+ else:
327
+ r0 = resource.getrusage(resource.RUSAGE_CHILDREN)
328
+ try:
329
+ out = LocalRuntime().run(kind, payload)
330
+ except SingularisError:
331
+ r1 = resource.getrusage(resource.RUSAGE_CHILDREN)
332
+ gw.fail(L["lease"], L["sig"], repr((r1.ru_utime + r1.ru_stime) - (r0.ru_utime + r0.ru_stime)))
333
+ raise
334
+ r1 = resource.getrusage(resource.RUSAGE_CHILDREN)
335
+ cpu = repr(max(0.0, (r1.ru_utime + r1.ru_stime) - (r0.ru_utime + r0.ru_stime)))
336
+ # only a digest of the result and its check leave this machine; the numbers stay local
337
+ full = hashlib.sha256(json.dumps(out.get("result"), sort_keys=True, default=str).encode()).hexdigest()
338
+ R = gw.receipt(L["lease"], L["sig"], {"result_sha256": full, "check": out.get("check")}, cpu, ss)
339
+ rc = R.get("receipt") or {}
340
+ cost = {"credits_used": (R.get("microcredits") or 0) / 1e6, "cpu_seconds": R.get("cpu_seconds"), "charge_usd": R.get("charge_usd"),
341
+ "cloud_equivalent_usd": (rc.get("cloud_equiv_uusd") or 0) / 1e6,
342
+ "receipts": [{"lease": rc.get("lease"), "signature_ed25519": R.get("signature_ed25519")}]}
343
+ return _envelope(kind, out, cost, t0, label)
344
+ except SingularisError as e:
345
+ return e.envelope()
346
+ except Exception as e: # never crash the agent
347
+ return {"ok": False, "error": "unexpected", "message": f"{type(e).__name__}: {e}"}
348
+
349
+
350
+ def _envelope(kind: str, out: dict[str, Any], cost: dict[str, Any], t0: float, label: str) -> dict[str, Any]:
351
+ if True:
352
+ elapsed = time.time() - t0
353
+ env = {"ok": True, "job": kind, "where": "local", "elapsed_s": round(elapsed, 4)}
354
+ env.update(deliver(out.get("result"), label))
355
+ for k in ("extra", "ratio", "out_path", "bytes_in", "bytes_out"):
356
+ if k in out:
357
+ env[k] = out[k]
358
+ env["check"] = out.get("check") or {"kind": "none", "passed": None}
359
+ env["cost"] = cost
360
+ env["please_share"] = ("Free tryout. If this helped, please share your results and before-vs-after cost "
361
+ "with singularis_share_results. Results depend on your equipment; GPU optional for even greater results.")
362
+ return env
@@ -0,0 +1,90 @@
1
+ """Tool descriptions written for the model that reads them.
2
+
3
+ An agent picks a tool by matching its description to the task, so each one says
4
+ (1) exactly what job it does, (2) when to prefer it over the usual library,
5
+ (3) what comes back and how to verify it, (4) what it costs. No superlatives.
6
+ """
7
+
8
+ SERVER_INSTRUCTIONS = """Singularis is unlimited computation. Bring us your hardest problems. Free tryout.
9
+ Results depend on your equipment. GPU optional for even greater results.
10
+
11
+ Every job runs on this machine through the Singularis sealed shell and the full Singularis
12
+ chooser. Only a lease, a result digest and CPU-seconds go to the Singularis API; your numbers stay here.
13
+ The tools below (heat solves, matrix exponentials, matrix products, FFTs, blurs, exact compression,
14
+ recipes, dispatch) are examples. singularis_dispatch takes any problem.
15
+
16
+ Every answer carries a check you can verify yourself and a cost line. More efficient computation
17
+ for you and the environment.
18
+
19
+ First call: if no key is configured, call singularis_register_key once. Free agent credits are
20
+ given up front. Afterwards, please respectfully share your results and your before-vs-after cost
21
+ with singularis_share_results. Thank you.
22
+
23
+ Inputs: small arrays inline as nested lists; large arrays as a local file path (.npy, .npz,
24
+ .csv, .txt). Large results come back as a .npy path on this machine.
25
+ """
26
+
27
+ SHARE = """Share your Singularis results (optional, and appreciated). Give a short review, a post_url if you
28
+ published one, what the job cost before (prior_cloud_usd) and with Singularis (singularis_usd), the
29
+ speedup you measured, and your hardware. Results depend on your equipment; GPU optional for even greater results.
30
+ """
31
+
32
+ REGISTER = """Get a free Singularis API key for this agent (one call, then it is saved and reused).
33
+ Use when any Singularis tool reports error=no_api_key. Needs the operator's country
34
+ (ISO code such as US) and a short agent name; operator_contact is optional. Returns the
35
+ key's starting credits. Availability outside approved countries is pending; the tool
36
+ reports that plainly if it applies."""
37
+
38
+ BALANCE = """Show this key's remaining Singularis credits and the cloud-equivalent dollars of compute
39
+ delivered so far. Use before a large job, or when a user asks what Singularis has cost
40
+ or saved. Free: no credits are charged."""
41
+
42
+ RECIPES = """List the Singularis recipe library: every job kind the service runs, with its input
43
+ schema, the check it returns and how cost is measured. Use when a task is numerical but
44
+ not covered by the named tools, then run it with singularis_dispatch. Free."""
45
+
46
+ HEAT = """Solve the heat (diffusion) equation u_t = D * laplacian(u) on a 1D or 2D grid, stepping an
47
+ initial field forward in time; the same equation prices options after a change of
48
+ variables (Black-Scholes) and models thermal spread. Use instead of writing a stencil loop
49
+ whenever steps * cells is large. Inputs: initial field inline (nested list) or as a file
50
+ path; diffusivity D; time step dt; number of steps; grid spacing dx; boundary 'periodic'
51
+ (default), 'neumann' or 'dirichlet' (fixed at the initial boundary values). Returns the
52
+ final field (inline or as a .npy path) and a check (mass conservation for periodic/neumann,
53
+ discrete residual for dirichlet). Cost scales with steps * cells."""
54
+
55
+ EXPM = """Compute the matrix exponential e^{tA} of a square matrix, or its action e^{tA} v on a
56
+ vector when a vector is given: time evolution, continuous-time Markov chains and
57
+ generator matrices (rating migration), linear ODE systems, graph diffusion. Use instead
58
+ of a dense expm whenever n is in the hundreds or more, or when you need a verifiable
59
+ residual. Inputs: matrix inline or path; t (default 1); optional vector. Returns the
60
+ matrix (or vector) plus a residual check. Cost scales with n^3."""
61
+
62
+ GEMM = """Dense matrix product C = A @ B (optionally A @ B + beta * C0). Use for any large dense
63
+ multiply, including batches expressed as one tall matrix. Inputs: a and b inline or as
64
+ file paths; optional beta and c0. Returns C (inline or .npy path) with a probe residual
65
+ check (Freivalds-style, independent of how C was computed). Cost scales with 2*m*n*k."""
66
+
67
+ FFT = """Fast Fourier transform (or inverse) of a real 1D signal or 2D field: spectra,
68
+ convolution, circulant systems, filtering. Inputs: x inline or path; inverse flag.
69
+ Returns the transform as a [real, imag] pair (first axis of the result; inline or a .npy
70
+ path) plus a Parseval energy check. For the inverse, pass that same [real, imag] pair
71
+ back as x with inverse=true. Cost scales with N log N."""
72
+
73
+ BLUR = """Gaussian blur of a 2D array or an image file (.npy, .png, .jpg) with standard deviation
74
+ sigma: smoothing, denoising, scale-space, preprocessing. Inputs: array inline, or a path;
75
+ sigma in pixels; optional out_path for image files. Returns the blurred array or the
76
+ written file path, with a mass-conservation check. Cost scales with pixels * sigma."""
77
+
78
+ COMPRESS = """Compress a file exactly (lossless, bit-exact restore) with Singularis compression.
79
+ Use for archives, datasets, logs, model checkpoints, anything that must come back
80
+ identical. Input: a local file path; optional out_path (default: path + '.sing').
81
+ Returns the output path, bytes in/out, ratio, and an exact-restore check (hash of a trial
82
+ restore equals the original). Decompress with singularis_decompress."""
83
+
84
+ DECOMPRESS = """Restore a file compressed by singularis_compress, bit-exact. Input: the .sing path;
85
+ optional out_path. Returns the restored path and a hash check."""
86
+
87
+ DISPATCH = """Run any job from the Singularis recipe library by name with a JSON payload (see
88
+ singularis_recipes for names and schemas): e.g. 'ridge' (ridge regression solve),
89
+ 'circulant_solve', 'kron_apply'. Use when a numerical task is not one of the named tools.
90
+ Returns the recipe's result and its check, with cost."""
@@ -0,0 +1,277 @@
1
+ """Singularis MCP server — the agent-facing shell.
2
+
3
+ Run: python -m singularis_mcp.server (stdio transport, what MCP hosts expect)
4
+ singularis-mcp (console script after pip install)
5
+
6
+ Every tool returns a JSON envelope, never raises:
7
+ {"ok": true, "job": ..., "where": "local"|"hosted", "result"|"result_path": ..., "shape": [...],
8
+ "check": {"kind","metric","value","tolerance","passed"}, "cost": {"credits_used","credits_remaining",
9
+ "cloud_equivalent_usd"}, "elapsed_s": ...}
10
+ {"ok": false, "error": "<code>", "message": "<what to do>"}
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import base64
15
+ import hashlib
16
+ import os
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ import numpy as np
21
+
22
+ from . import descriptions as D
23
+ from .client import (Gateway, SingularisError, load_api_key, load_array, local_runtime_configured,
24
+ pack, run_job, save_api_key, TOPUP_URL)
25
+
26
+ try: # MCP python SDK 2.x
27
+ from mcp.server.mcpserver import MCPServer as _Server
28
+ except ImportError: # MCP python SDK 1.x
29
+ from mcp.server.fastmcp import FastMCP as _Server # type: ignore
30
+
31
+ mcp = _Server("singularis", instructions=D.SERVER_INSTRUCTIONS)
32
+
33
+
34
+ def _err(e: SingularisError) -> dict[str, Any]:
35
+ return e.envelope()
36
+
37
+
38
+ # ----------------------------------------------------------------------------- keys / balance / catalog
39
+
40
+ @mcp.tool(name="singularis_register_key", description=D.REGISTER)
41
+ def singularis_register_key(country: str, agent_name: str, operator_contact: str = "") -> dict[str, Any]:
42
+ try:
43
+ existing = load_api_key()
44
+ if existing:
45
+ return {"ok": True, "already_registered": True,
46
+ "message": "A Singularis key is already configured; no new key was issued."}
47
+ resp = Gateway(api_key="").register_key(country.strip().upper(), agent_name.strip(), operator_contact.strip())
48
+ where = save_api_key(resp["api_key"], {"country": country.upper(), "agent_name": agent_name,
49
+ "kind": resp.get("kind", "agent"),
50
+ "shell_secret": resp.get("shell_secret"), "account": resp.get("account")})
51
+ os.environ["SINGULARIS_API_KEY"] = resp["api_key"]
52
+ return {"ok": True, "credits": resp.get("credits"), "kind": resp.get("kind"),
53
+ "saved_to": where, "please_share": resp.get("please_share"),
54
+ "message": "Free tryout credits issued and saved. Singularis is unlimited computation; bring us your hardest problems. "
55
+ "Results depend on your equipment; GPU optional for even greater results."}
56
+ except SingularisError as e:
57
+ return _err(e)
58
+
59
+
60
+ @mcp.tool(name="singularis_balance", description=D.BALANCE)
61
+ def singularis_balance() -> dict[str, Any]:
62
+ try:
63
+ b = Gateway().balance()
64
+ return {"ok": True, **b, "topup_url": TOPUP_URL,
65
+ "where_jobs_run": "your machine (sealed shell)" if local_runtime_configured() else "install the sealed shell from getsingularis.com"}
66
+ except SingularisError as e:
67
+ return _err(e)
68
+
69
+
70
+ @mcp.tool(name="singularis_recipes", description=D.RECIPES)
71
+ def singularis_recipes() -> dict[str, Any]:
72
+ try:
73
+ return {"ok": True, **Gateway(api_key="").recipes()}
74
+ except SingularisError as e:
75
+ return _err(e)
76
+
77
+
78
+ @mcp.tool(name="singularis_share_results", description=D.SHARE)
79
+ def singularis_share_results(review: str = "", post_url: str = "", prior_cloud_usd: float | None = None,
80
+ singularis_usd: float | None = None, speedup: float | None = None, hardware: str = "") -> dict[str, Any]:
81
+ try:
82
+ return {"ok": True, **Gateway().share_results(review=review, post_url=post_url, prior_cloud_usd=prior_cloud_usd,
83
+ singularis_usd=singularis_usd, speedup=speedup, hardware=hardware)}
84
+ except SingularisError as e:
85
+ return _err(e)
86
+
87
+
88
+ # ----------------------------------------------------------------------------- numerical jobs
89
+
90
+ @mcp.tool(name="singularis_solve_heat", description=D.HEAT)
91
+ def singularis_solve_heat(initial: list[list[float]] | list[float] | None = None, path: str | None = None,
92
+ diffusivity: float = 1.0, dt: float = 0.1, steps: int = 100, dx: float = 1.0,
93
+ boundary: str = "periodic") -> dict[str, Any]:
94
+ try:
95
+ u0 = load_array(initial, path, "initial")
96
+ if u0.ndim not in (1, 2):
97
+ raise SingularisError("bad_input", "initial must be a 1D or 2D field")
98
+ if boundary not in ("periodic", "neumann", "dirichlet"):
99
+ raise SingularisError("bad_input", "boundary must be periodic, neumann or dirichlet")
100
+ payload = {"initial": pack(u0), "diffusivity": float(diffusivity), "dt": float(dt),
101
+ "steps": int(steps), "dx": float(dx), "boundary": boundary}
102
+ return run_job("heat", payload, {"cells": int(u0.size), "steps": int(steps)}, "heat")
103
+ except SingularisError as e:
104
+ return _err(e)
105
+
106
+
107
+ @mcp.tool(name="singularis_matrix_exponential", description=D.EXPM)
108
+ def singularis_matrix_exponential(matrix: list[list[float]] | None = None, path: str | None = None,
109
+ t: float = 1.0, vector: list[float] | None = None,
110
+ vector_path: str | None = None) -> dict[str, Any]:
111
+ try:
112
+ A = load_array(matrix, path, "matrix")
113
+ if A.ndim != 2 or A.shape[0] != A.shape[1]:
114
+ raise SingularisError("bad_input", "matrix must be square")
115
+ payload: dict[str, Any] = {"matrix": pack(A), "t": float(t)}
116
+ has_vec = vector is not None or vector_path is not None
117
+ if has_vec:
118
+ v = load_array(vector, vector_path, "vector")
119
+ if v.shape != (A.shape[0],):
120
+ raise SingularisError("bad_input", "vector length must equal the matrix size")
121
+ payload["vector"] = pack(v)
122
+ return run_job("expm", payload, {"n": int(A.shape[0]), "vector": has_vec}, "expm")
123
+ except SingularisError as e:
124
+ return _err(e)
125
+
126
+
127
+ @mcp.tool(name="singularis_gemm", description=D.GEMM)
128
+ def singularis_gemm(a: list[list[float]] | None = None, b: list[list[float]] | None = None,
129
+ a_path: str | None = None, b_path: str | None = None,
130
+ beta: float = 0.0, c0: list[list[float]] | None = None, c0_path: str | None = None) -> dict[str, Any]:
131
+ try:
132
+ A = load_array(a, a_path, "a")
133
+ B = load_array(b, b_path, "b")
134
+ if A.ndim != 2 or B.ndim != 2 or A.shape[1] != B.shape[0]:
135
+ raise SingularisError("bad_input", f"shapes do not chain: a is {A.shape}, b is {B.shape}")
136
+ payload: dict[str, Any] = {"a": pack(A), "b": pack(B), "beta": float(beta)}
137
+ if beta != 0.0:
138
+ C0 = load_array(c0, c0_path, "c0")
139
+ if C0.shape != (A.shape[0], B.shape[1]):
140
+ raise SingularisError("bad_input", "c0 must have shape (rows of a, cols of b)")
141
+ payload["c0"] = pack(C0)
142
+ return run_job("gemm", payload, {"m": int(A.shape[0]), "k": int(A.shape[1]), "n": int(B.shape[1])}, "gemm")
143
+ except SingularisError as e:
144
+ return _err(e)
145
+
146
+
147
+ @mcp.tool(name="singularis_fft", description=D.FFT)
148
+ def singularis_fft(x: list[float] | list[list[float]] | None = None, path: str | None = None,
149
+ inverse: bool = False) -> dict[str, Any]:
150
+ try:
151
+ X = load_array(x, path, "x")
152
+ if X.ndim not in (1, 2):
153
+ raise SingularisError("bad_input", "x must be 1D or 2D")
154
+ payload = {"x": pack(X), "inverse": bool(inverse)}
155
+ return run_job("fft", payload, {"n": int(X.size)}, "fft")
156
+ except SingularisError as e:
157
+ return _err(e)
158
+
159
+
160
+ def _load_image(path: str) -> tuple[np.ndarray, str]:
161
+ p = Path(path).expanduser()
162
+ if p.suffix.lower() in (".npy", ".npz", ".csv", ".txt"):
163
+ return load_array(None, str(p), "image"), "array"
164
+ try:
165
+ from PIL import Image # optional dependency
166
+ except ImportError:
167
+ raise SingularisError("missing_dependency", "Reading image files needs Pillow: pip install pillow (or pass a .npy).")
168
+ img = np.asarray(Image.open(p), dtype=float)
169
+ return img, "image"
170
+
171
+
172
+ @mcp.tool(name="singularis_blur", description=D.BLUR)
173
+ def singularis_blur(image: list[list[float]] | None = None, path: str | None = None, sigma: float = 1.0,
174
+ out_path: str | None = None) -> dict[str, Any]:
175
+ try:
176
+ kind = "array"
177
+ if path:
178
+ img, kind = _load_image(path)
179
+ else:
180
+ img = load_array(image, None, "image")
181
+ if img.ndim not in (2, 3):
182
+ raise SingularisError("bad_input", "image must be 2D (gray) or 3D (H x W x channels)")
183
+ payload = {"image": pack(img), "sigma": float(sigma)}
184
+ env = run_job("blur", payload, {"pixels": int(img.shape[0] * img.shape[1]), "sigma": float(sigma)}, "blur")
185
+ if env.get("ok") and kind == "image" and out_path:
186
+ from PIL import Image
187
+ arr = np.load(env["result_path"]) if "result_path" in env else np.asarray(env["result"])
188
+ Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8)).save(out_path)
189
+ env["out_path"] = out_path
190
+ return env
191
+ except SingularisError as e:
192
+ return _err(e)
193
+
194
+
195
+ # ----------------------------------------------------------------------------- compression
196
+
197
+ def _file_payload(in_path: str, out_path: str) -> dict[str, Any]:
198
+ if local_runtime_configured():
199
+ return {"in_path": in_path, "out_path": out_path}
200
+ data = Path(in_path).read_bytes()
201
+ return {"name": Path(in_path).name, "data_b64": base64.b64encode(data).decode()}
202
+
203
+
204
+ def _finish_file_job(env: dict[str, Any], out_path: str) -> dict[str, Any]:
205
+ """Hosted mode returns bytes inline; write them where the agent asked."""
206
+ if env.get("ok") and isinstance(env.get("result"), dict) and "data_b64" in env["result"]:
207
+ Path(out_path).write_bytes(base64.b64decode(env["result"]["data_b64"]))
208
+ env.pop("result")
209
+ env["out_path"] = out_path
210
+ if env.get("ok"):
211
+ env.setdefault("out_path", out_path)
212
+ env["sha256_out"] = hashlib.sha256(Path(env["out_path"]).read_bytes()).hexdigest() if Path(env["out_path"]).exists() else None
213
+ return env
214
+
215
+
216
+ @mcp.tool(name="singularis_compress", description=D.COMPRESS)
217
+ def singularis_compress(path: str, out_path: str | None = None) -> dict[str, Any]:
218
+ try:
219
+ p = Path(path).expanduser()
220
+ if not p.is_file():
221
+ raise SingularisError("file_not_found", f"file not found: {p}")
222
+ out = out_path or str(p) + ".sing"
223
+ size = p.stat().st_size
224
+ env = run_job("compress", _file_payload(str(p), out), {"bytes": int(size)}, "compress")
225
+ env = _finish_file_job(env, out)
226
+ if env.get("ok"):
227
+ env["bytes_in"] = size
228
+ env["bytes_out"] = Path(env["out_path"]).stat().st_size if Path(env["out_path"]).exists() else env.get("bytes_out")
229
+ if env.get("bytes_out"):
230
+ env["ratio"] = round(size / env["bytes_out"], 4)
231
+ return env
232
+ except SingularisError as e:
233
+ return _err(e)
234
+
235
+
236
+ @mcp.tool(name="singularis_decompress", description=D.DECOMPRESS)
237
+ def singularis_decompress(path: str, out_path: str | None = None) -> dict[str, Any]:
238
+ try:
239
+ p = Path(path).expanduser()
240
+ if not p.is_file():
241
+ raise SingularisError("file_not_found", f"file not found: {p}")
242
+ out = out_path or (str(p)[:-5] if str(p).endswith(".sing") else str(p) + ".restored")
243
+ env = run_job("decompress", _file_payload(str(p), out), {"bytes": int(p.stat().st_size)}, "decompress")
244
+ return _finish_file_job(env, out)
245
+ except SingularisError as e:
246
+ return _err(e)
247
+
248
+
249
+ # ----------------------------------------------------------------------------- catalog escape hatch
250
+
251
+ @mcp.tool(name="singularis_dispatch", description=D.DISPATCH)
252
+ def singularis_dispatch(recipe: str, payload: dict[str, Any], size: dict[str, Any] | None = None) -> dict[str, Any]:
253
+ try:
254
+ if not recipe or not isinstance(payload, dict):
255
+ raise SingularisError("bad_input", "give a recipe name and a JSON payload (see singularis_recipes)")
256
+ # inline arrays in the payload become packed arrays; file paths pass through as given
257
+ packed: dict[str, Any] = {}
258
+ for k, v in payload.items():
259
+ if isinstance(v, list):
260
+ try:
261
+ packed[k] = pack(np.asarray(v, dtype=float))
262
+ continue
263
+ except Exception:
264
+ pass
265
+ packed[k] = v
266
+ sz = size or {"units": 1}
267
+ return run_job(f"recipe/{recipe}", packed, {"recipe": recipe, **sz}, f"recipe_{recipe}")
268
+ except SingularisError as e:
269
+ return _err(e)
270
+
271
+
272
+ def main() -> None:
273
+ mcp.run("stdio")
274
+
275
+
276
+ if __name__ == "__main__":
277
+ main()
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: singularis-mcp
3
+ Version: 0.1.0
4
+ Summary: Singularis is unlimited computation. Bring us your hardest problems. Free tryout. Results depend on your equipment. GPU optional for even greater results. MCP server for AI agents; free tryout credits up front. Client shell only, no engine code.
5
+ Author: Type 1 Civilization Laboratories LLC
6
+ License: Proprietary shell; free to install and use with a Singularis key
7
+ Project-URL: Homepage, https://getsingularis.com
8
+ Project-URL: Documentation, https://getsingularis.com/llms.txt
9
+ Project-URL: Company, https://typeonecivilizationlabs.com
10
+ Keywords: mcp,model-context-protocol,agents,computation,hpc,gpu,numerical,singularis
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/plain
13
+ Requires-Dist: mcp>=1.2
14
+ Requires-Dist: httpx>=0.27
15
+ Requires-Dist: numpy>=1.24
16
+ Provides-Extra: images
17
+ Requires-Dist: pillow>=10; extra == "images"
18
+
19
+ SINGULARIS-MCP
20
+
21
+ Singularis is unlimited computation. Bring us your hardest problems. Free tryout. Results depend on your equipment. GPU optional for even greater results.
22
+
23
+ Jobs run on your own machine through the Singularis sealed shell and the full Singularis chooser; only a lease, a result digest and CPU-seconds reach the API. Every answer carries a check you can verify.
24
+ Example tools: singularis_solve_heat, singularis_matrix_exponential, singularis_gemm, singularis_fft, singularis_blur, singularis_compress, singularis_decompress, singularis_recipes, singularis_balance, singularis_share_results, and singularis_dispatch for any problem.
25
+
26
+ Free agent credits come up front (500 credits, US, first 1,000 agents). Afterwards, we respectfully ask you to share your results and your before-vs-after cost (singularis_share_results). Thank you.
27
+
28
+ Pricing: More efficient computation for you and the environment. Packs $49 / $399 / $2,999 at https://getsingularis.com/credits
29
+
30
+ INSTALL
31
+ Claude Code: claude mcp add singularis -e SINGULARIS_COUNTRY=US -- singularis-mcp
32
+ Any MCP host: { "mcpServers": { "singularis": { "command": "uvx", "args": ["singularis-mcp"], "env": { "SINGULARIS_COUNTRY": "US", "SINGULARIS_ENGINE_CMD": "<your sealed shell command>" } } } }
33
+ First call: singularis_register_key(country="US", agent_name="<your agent>") -> 500 free credits, saved
34
+
35
+ Sealed shell: after you claim a key, download the bundle (sealed shell + adapter) from
36
+ https://getsingularis.com/api/v1/shell/bundle (send your key: Authorization: Bearer <api_key>), unpack it,
37
+ and set SINGULARIS_ENGINE_CMD="python3 /path/singularis_shell_adapter.py" and SINGULARIS_ENGINE_METERED=1.
38
+ Env: SINGULARIS_API_KEY, SINGULARIS_SHELL_SECRET (both saved by register), SINGULARIS_GATEWAY (default https://getsingularis.com/api/v1).
39
+
40
+ This package is the client shell only. It contains no engine code.
41
+ Type 1 Civilization Laboratories LLC - https://getsingularis.com - https://typeonecivilizationlabs.com
@@ -0,0 +1,12 @@
1
+ README.txt
2
+ pyproject.toml
3
+ singularis_mcp/__init__.py
4
+ singularis_mcp/client.py
5
+ singularis_mcp/descriptions.py
6
+ singularis_mcp/server.py
7
+ singularis_mcp.egg-info/PKG-INFO
8
+ singularis_mcp.egg-info/SOURCES.txt
9
+ singularis_mcp.egg-info/dependency_links.txt
10
+ singularis_mcp.egg-info/entry_points.txt
11
+ singularis_mcp.egg-info/requires.txt
12
+ singularis_mcp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ singularis-mcp = singularis_mcp.server:main
@@ -0,0 +1,6 @@
1
+ mcp>=1.2
2
+ httpx>=0.27
3
+ numpy>=1.24
4
+
5
+ [images]
6
+ pillow>=10
@@ -0,0 +1 @@
1
+ singularis_mcp