clikernel 0.2.9__tar.gz → 0.2.11__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.
@@ -2,6 +2,20 @@
2
2
 
3
3
  <!-- do not remove -->
4
4
 
5
+ ## 0.2.11
6
+
7
+ ### New Features
8
+
9
+ - Rewrite module and API docs for clarity, harden session cleanup with try/finally and grouped errors, negotiate MCP protocol version ([#49](https://github.com/AnswerDotAI/clikernel/issues/49))
10
+
11
+
12
+ ## 0.2.10
13
+
14
+ ### New Features
15
+
16
+ - Add Luau kernel support: lua tool and create(language=...), Python-only startup/inspectors ([#48](https://github.com/AnswerDotAI/clikernel/issues/48))
17
+
18
+
5
19
  ## 0.2.9
6
20
 
7
21
  ### Breaking Changes
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: clikernel
3
+ Version: 0.2.11
4
+ Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
5
+ Author: clikernel contributors
6
+ License: Apache-2.0
7
+ Project-URL: Repository, https://github.com/AnswerDotAI/clikernel
8
+ Project-URL: Documentation, https://AnswerDotAI.github.io/clikernel/
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: fastcore>=2.2.2
15
+ Requires-Dist: jupyasyncclient>=0.2.10
16
+ Requires-Dist: jupywire>=0.1.9
17
+ Requires-Dist: mcpmini>=0.0.5
18
+ Requires-Dist: rustygate>=0.1.11
19
+ Requires-Dist: httpx
20
+ Provides-Extra: dev
21
+ Requires-Dist: fastship; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # clikernel
25
+
26
+
27
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
28
+
29
+ `clikernel` gives an LLM agent a persistent Python or Luau session. Imports, variables, and results remain available between tool calls. An agent can create a kernel or attach to an existing one, including a user’s live solveit kernel, on a local or named remote gateway.
30
+
31
+ [rustygate](https://github.com/AnswerDotAI/rustygate) hosts the Jupyter kernels and provides their MCP tools. `clikernel` is the conversation-side router: the MCP host launches it over stdio, and it forwards requests to gateways over HTTP. It selects gateways from `gateways.toml`, supplies `startup.py` and `inspectors.py` to Python kernels it creates, and starts a private local gateway when needed. The default kernel is [ipymini](https://github.com/AnswerDotAI/ipymini).
32
+
33
+ Kernel ownership determines what happens when the conversation ends:
34
+
35
+ - A session closes kernels it created with `autoclose`, including the automatic kernel used by bare `py` or `lua` and kernels created with `create`’s default settings.
36
+ - Attaching with `use_kernel` does not make that session responsible for closing the kernel.
37
+ - A kernel created with `autoclose=false` on a resident gateway can outlive the conversation. A later conversation can attach and continue using its state.
38
+ - A private child gateway ends with its conversation. Use a resident gateway for kernels that need to survive across conversations.
39
+
40
+ ## Install
41
+
42
+ ``` sh
43
+ pip install clikernel
44
+ ```
45
+
46
+ This installs rustygate and ipymini. No service setup is required for a conversation-local kernel: clikernel starts a private gateway if it cannot find one. To retain kernels across conversations, run a resident gateway, for example through launchd or systemd:
47
+
48
+ ``` sh
49
+ rustygate --port 8787
50
+ ```
51
+
52
+ ## Use with an MCP host
53
+
54
+ Register the stdio server with your MCP host. For Claude Code:
55
+
56
+ ``` sh
57
+ claude mcp add clikernel -- clikernel-mcp
58
+ ```
59
+
60
+ Use `py(code=...)` for Python/IPython or `lua(code=...)` for bundled Luau. Either starts its language’s kernel when none is current. There is one current kernel: a language mismatch errors without switching or running the code. IPython magics, including `%%bash`, are Python-only.
61
+
62
+ For a named kernel, use `create(dlgname="work", language="luau")`. Omit `language` to reuse an existing binding unchanged, or default a new kernel to Python. An explicit language must match an existing binding. The other tools are `list_kernels`, `use_kernel`, `delete_kernel`, `restart`, and `interrupt`; creation, selection, and listing report the language.
63
+
64
+ These tools forward to rustygate. `list_kernels`, `use_kernel`, and `create` also accept a `host` naming a gateway from `gateways.toml`. One MCP registration can therefore reach multiple machines. Replies retain the gateway’s text and image blocks. Python startup and inspectors run only in Python, never Luau, including after restart.
65
+
66
+ `$CLIKERNEL_HOST` overrides the default gateway URL, `http://127.0.0.1:8787`. If no gateway answers, clikernel uses a private child gateway for the conversation. The ownership rules above determine which kernels close at session end.
67
+
68
+ Pass `--quiet`, as in `clikernel-mcp --quiet`, to omit startup output from replies. Python startup code still runs.
69
+
70
+ ## Configuration
71
+
72
+ Three optional files in `$XDG_CONFIG_HOME/clikernel/` configure the router, usually under `~/.config/clikernel/`:
73
+
74
+ - `startup.py` runs in each Python kernel clikernel creates, with `__file__` set to its path. Its output appears in the reply announcing the kernel unless `--quiet` is set.
75
+ - `inspectors.py` installs Python cell inspectors after startup. Define `inspect`, a list named `inspectors`, or both. Each inspector runs once before a cell: a one-argument inspector takes the cell’s AST, and a two-argument inspector takes the AST and raw source. Return a string to print a note before the output. Raise the provided `RuleBlock` to block execution. Other exceptions produce a warning and allow the cell to run. See [examples/inspectors.py](examples/inspectors.py).
76
+ - `gateways.toml` names remote gateways and configures authentication without putting tokens in tool arguments:
77
+
78
+ ``` toml
79
+ [gateways.solveit]
80
+ url = "https://solveit.example.com/gate"
81
+ token_env = "SOLVEIT_TOKEN"
82
+ verify = false # optional: accept a self-signed certificate
83
+ ```
84
+
85
+ ## The stream protocol
86
+
87
+ Run `clikernel` as a plain CLI process for clients that read a text stream rather than MCP messages. It uses a delimiter-framed stdin/stdout protocol:
88
+
89
+ - Input is not echoed.
90
+ - Each request gets a `.` acknowledgement.
91
+ - A per-process random delimiter marks the end of each response.
92
+ - Multiline cells are framed by `--` and the delimiter.
93
+
94
+ The startup banner supplies the protocol instructions and delimiter. Running `clikernel` without arguments creates a kernel and stops it on exit. `--kernel <id>` attaches to an existing kernel and leaves it running on exit.
@@ -0,0 +1,71 @@
1
+ # clikernel
2
+
3
+
4
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
5
+
6
+ `clikernel` gives an LLM agent a persistent Python or Luau session. Imports, variables, and results remain available between tool calls. An agent can create a kernel or attach to an existing one, including a user’s live solveit kernel, on a local or named remote gateway.
7
+
8
+ [rustygate](https://github.com/AnswerDotAI/rustygate) hosts the Jupyter kernels and provides their MCP tools. `clikernel` is the conversation-side router: the MCP host launches it over stdio, and it forwards requests to gateways over HTTP. It selects gateways from `gateways.toml`, supplies `startup.py` and `inspectors.py` to Python kernels it creates, and starts a private local gateway when needed. The default kernel is [ipymini](https://github.com/AnswerDotAI/ipymini).
9
+
10
+ Kernel ownership determines what happens when the conversation ends:
11
+
12
+ - A session closes kernels it created with `autoclose`, including the automatic kernel used by bare `py` or `lua` and kernels created with `create`’s default settings.
13
+ - Attaching with `use_kernel` does not make that session responsible for closing the kernel.
14
+ - A kernel created with `autoclose=false` on a resident gateway can outlive the conversation. A later conversation can attach and continue using its state.
15
+ - A private child gateway ends with its conversation. Use a resident gateway for kernels that need to survive across conversations.
16
+
17
+ ## Install
18
+
19
+ ``` sh
20
+ pip install clikernel
21
+ ```
22
+
23
+ This installs rustygate and ipymini. No service setup is required for a conversation-local kernel: clikernel starts a private gateway if it cannot find one. To retain kernels across conversations, run a resident gateway, for example through launchd or systemd:
24
+
25
+ ``` sh
26
+ rustygate --port 8787
27
+ ```
28
+
29
+ ## Use with an MCP host
30
+
31
+ Register the stdio server with your MCP host. For Claude Code:
32
+
33
+ ``` sh
34
+ claude mcp add clikernel -- clikernel-mcp
35
+ ```
36
+
37
+ Use `py(code=...)` for Python/IPython or `lua(code=...)` for bundled Luau. Either starts its language’s kernel when none is current. There is one current kernel: a language mismatch errors without switching or running the code. IPython magics, including `%%bash`, are Python-only.
38
+
39
+ For a named kernel, use `create(dlgname="work", language="luau")`. Omit `language` to reuse an existing binding unchanged, or default a new kernel to Python. An explicit language must match an existing binding. The other tools are `list_kernels`, `use_kernel`, `delete_kernel`, `restart`, and `interrupt`; creation, selection, and listing report the language.
40
+
41
+ These tools forward to rustygate. `list_kernels`, `use_kernel`, and `create` also accept a `host` naming a gateway from `gateways.toml`. One MCP registration can therefore reach multiple machines. Replies retain the gateway’s text and image blocks. Python startup and inspectors run only in Python, never Luau, including after restart.
42
+
43
+ `$CLIKERNEL_HOST` overrides the default gateway URL, `http://127.0.0.1:8787`. If no gateway answers, clikernel uses a private child gateway for the conversation. The ownership rules above determine which kernels close at session end.
44
+
45
+ Pass `--quiet`, as in `clikernel-mcp --quiet`, to omit startup output from replies. Python startup code still runs.
46
+
47
+ ## Configuration
48
+
49
+ Three optional files in `$XDG_CONFIG_HOME/clikernel/` configure the router, usually under `~/.config/clikernel/`:
50
+
51
+ - `startup.py` runs in each Python kernel clikernel creates, with `__file__` set to its path. Its output appears in the reply announcing the kernel unless `--quiet` is set.
52
+ - `inspectors.py` installs Python cell inspectors after startup. Define `inspect`, a list named `inspectors`, or both. Each inspector runs once before a cell: a one-argument inspector takes the cell’s AST, and a two-argument inspector takes the AST and raw source. Return a string to print a note before the output. Raise the provided `RuleBlock` to block execution. Other exceptions produce a warning and allow the cell to run. See [examples/inspectors.py](examples/inspectors.py).
53
+ - `gateways.toml` names remote gateways and configures authentication without putting tokens in tool arguments:
54
+
55
+ ``` toml
56
+ [gateways.solveit]
57
+ url = "https://solveit.example.com/gate"
58
+ token_env = "SOLVEIT_TOKEN"
59
+ verify = false # optional: accept a self-signed certificate
60
+ ```
61
+
62
+ ## The stream protocol
63
+
64
+ Run `clikernel` as a plain CLI process for clients that read a text stream rather than MCP messages. It uses a delimiter-framed stdin/stdout protocol:
65
+
66
+ - Input is not echoed.
67
+ - Each request gets a `.` acknowledgement.
68
+ - A per-process random delimiter marks the end of each response.
69
+ - Multiline cells are framed by `--` and the delimiter.
70
+
71
+ The startup banner supplies the protocol instructions and delimiter. Running `clikernel` without arguments creates a kernel and stops it on exit. `--kernel <id>` attaches to an existing kernel and leaves it running on exit.
@@ -4,8 +4,8 @@ Modules:
4
4
 
5
5
  - `clikernel.cli`: The stream-protocol frontend: the service on stdin/stdout for token-reading clients
6
6
  - `clikernel.core`: Gateway naming, startup delivery, and MCP sessions on rustygate gateways
7
- - `clikernel.mcp`: The MCP frontend: a stdio router over rustygate gateways
7
+ - `clikernel.mcp`: Route stdio MCP requests to rustygate gateways
8
8
  - `clikernel.skill`: Use the `clikernel` MCP session as the default workspace for Python work: reading and changing files, notebook work, trying things out, checking how a library behaves, and reshaping data. One session stays open, so imports and variables carry between calls. Read this before writing, running, or debugging Python code in a session with `clikernel` connected.
9
9
  - `clikernel.stream`: Streaming JSON-lines worker protocol: nbformat-shaped output events, and a supervisor for select-based UIs."""
10
10
 
11
- __version__ = "0.2.9"
11
+ __version__ = "0.2.11"
@@ -147,7 +147,9 @@ def main(
147
147
  except KeyboardInterrupt: asyncio.run_coroutine_threadsafe(g.call('interrupt'), loop)
148
148
  try: serve_stream(execute, info=info, should_exit=lambda: stop)
149
149
  finally:
150
- run(g.aclose()) # ends the session: the kernel this run created dies with it, an attached one survives
151
- if child: child.stop()
152
- loop.call_soon_threadsafe(loop.stop)
150
+ try: run(g.aclose())
151
+ finally:
152
+ try:
153
+ if child: child.stop()
154
+ finally: loop.call_soon_threadsafe(loop.stop)
153
155
 
@@ -1,6 +1,12 @@
1
1
  """Gateway naming, startup delivery, and MCP sessions on rustygate gateways
2
2
 
3
- clikernel is the LLM side of a two-process design: a gateway ([rustygate](https://github.com/AnswerDotAI/rustygate)) hosts the kernels and serves MCP itself at `POST /mcp`; clikernel starts and stops with each conversation and routes the harness's stdio MCP to gateways. This module is the client layer: gateway naming from `gateways.toml`, the per-session kernel-creation defaults (the conversation's cwd and environment, with `startup.py` and `inspectors.py` composed into one startup source), `Gateway` — one MCP session on one gateway — and `default_gateway`, which finds the local gateway or starts an owned child that lives exactly as long as the conversation. Kernel lifecycle policy lives gateway-side: ending a session stops the kernels it created with autoclose and nothing else.
3
+ [Rustygate](https://github.com/AnswerDotAI/rustygate) hosts kernels and serves MCP requests at `POST /mcp`. Clikernel runs alongside it for the duration of a conversation. It routes the LLM client's stdio MCP requests to gateways.
4
+
5
+ This module connects to those gateways. `gateways.toml` gives them names. `session_defaults` supplies kernel startup code and, for a local gateway, the conversation's working directory and environment. It combines `startup.py` and `inspectors.py` into source that can run on another machine.
6
+
7
+ `Gateway` represents one MCP session on one gateway. `default_gateway` connects to the local gateway or starts a child process if it cannot connect. The caller must stop any returned child. The MCP router does this when the conversation ends.
8
+
9
+ Rustygate tracks the session's current kernel and which kernels it created. Ending a session stops its autoclose kernels. It leaves other kernels running. Stopping an owned gateway process stops all kernels in that process.
4
10
 
5
11
  Docs: https://AnswerDotAI.github.io/clikernel/core.html.md"""
6
12
 
@@ -21,26 +27,27 @@ from . import __version__
21
27
  DEFAULT_URL = 'http://127.0.0.1:8787'
22
28
 
23
29
  def cfg_dir():
24
- "The clikernel config directory"
30
+ "Return the clikernel configuration directory."
25
31
  return xdg_config_home()/'clikernel'
26
32
 
27
33
  def gateways(cfgdir=None):
28
- "Named gateways from `gateways.toml`: `{name: {url, token | token_env, verify}}`"
34
+ "Read named gateways as `{name: {url, token | token_env, verify}}` from `gateways.toml`."
29
35
  p = (Path(cfgdir) if cfgdir else cfg_dir())/'gateways.toml'
30
36
  return tomllib.loads(p.read_text()).get('gateways', {}) if p.exists() else {}
31
37
 
32
38
  def resolve(host='', cfgdir=None):
33
- "`(url, token, verify)` for `host`: empty = the default local gateway, a URL = itself, else a `gateways.toml` name"
39
+ "Resolve an empty host, URL, or configured gateway name to `(url, token, verify)`."
34
40
  if not host: return os.environ.get('CLIKERNEL_HOST', DEFAULT_URL), os.environ.get('CLIKERNEL_TOKEN'), True
35
41
  if '://' in host: return host, os.environ.get('CLIKERNEL_TOKEN'), True
42
+ cfgdir = Path(cfgdir) if cfgdir else cfg_dir()
36
43
  g = gateways(cfgdir).get(host)
37
- if g is None: raise ValueError(f"unknown gateway {host!r}: not a URL, and not in {cfg_dir()/'gateways.toml'}")
44
+ if g is None: raise ValueError(f"unknown gateway {host!r}: not a URL, and not in {cfgdir/'gateways.toml'}")
38
45
  return g['url'], g.get('token') or os.environ.get(g.get('token_env','')) or None, g.get('verify', True)
39
46
 
40
47
 
41
48
  # %% ../nbs/00_core.ipynb #0d846754
42
49
  def _startup_src(src, path):
43
- "The startup file's source wrapped so `__file__` is bound to its path during the run, and absent after"
50
+ "Wrap startup source with `__file__` set to its path during execution and deleted afterwards."
44
51
  return f'''__file__ = {str(path)!r}
45
52
  try: exec(compile({src!r}, __file__, 'exec'))
46
53
  finally: del __file__'''
@@ -92,7 +99,7 @@ def _inspector_setup(src):
92
99
 
93
100
  # %% ../nbs/00_core.ipynb #1362e6ce
94
101
  def startup_src(cfgdir=None):
95
- "The composed startup source for one kernel: `startup.py` wrapped, then the inspector installer"
102
+ "Combine wrapped `startup.py` source with the inspector installer, in that order."
96
103
  d = Path(cfgdir) if cfgdir else cfg_dir()
97
104
  parts = []
98
105
  if (p := d/'startup.py').exists(): parts.append(_startup_src(p.read_text(), p))
@@ -100,7 +107,7 @@ def startup_src(cfgdir=None):
100
107
  return '\n'.join(parts)
101
108
 
102
109
  def session_defaults(cfgdir=None, quiet=False, local=True):
103
- "The `rustygate` initialize extension: startup source and quiet, plus cwd and env for a local gateway"
110
+ "Return startup and quiet settings for rustygate initialization. Local sessions also include cwd and env."
104
111
  d = dict(startup=startup_src(cfgdir), quiet=quiet)
105
112
  if local:
106
113
  d['cwd'] = os.getcwd()
@@ -109,7 +116,7 @@ def session_defaults(cfgdir=None, quiet=False, local=True):
109
116
 
110
117
  # %% ../nbs/00_core.ipynb #10ed59fc
111
118
  class Gateway:
112
- "One MCP session on one rustygate: initialize with session defaults, call tools, DELETE at close"
119
+ "Open an MCP session on rustygate, call its tools, and end the session with DELETE."
113
120
  def __init__(self,
114
121
  url, # The gateway base URL, e.g. 'http://127.0.0.1:8787'
115
122
  token=None, # Gateway auth token, sent as a bearer token
@@ -120,17 +127,18 @@ class Gateway:
120
127
  self.tr = HTTPTransport(f"{url.rstrip('/')}/mcp", token=token, http_client=client)
121
128
 
122
129
  async def rpc(self, method, **params):
123
- "One JSON-RPC request, returning its result and raising on a protocol-level error"
130
+ "Send a JSON-RPC request and return its result. Raise `RuntimeError` for protocol errors."
124
131
  self._id += 1
125
132
  r = await self.tr.send(jreq(method, self._id, **params))
126
133
  if 'error' in r: raise RuntimeError(f"{r['error']['code']}: {r['error']['message']}")
127
134
  return r['result']
128
135
 
129
136
  async def initialize(self, defaults=None):
130
- "Open the MCP session, sending `defaults` as the `rustygate` extension; returns self"
137
+ "Open the MCP session with `defaults` in the `rustygate` extension. Return self."
131
138
  await self.tr.start()
132
139
  self.info = await self.rpc('initialize', protocolVersion='2025-11-25', capabilities={},
133
140
  clientInfo=dict(name='clikernel', version=__version__), rustygate=defaults or {})
141
+ self.tr.proto = self.info['protocolVersion']
134
142
  await self.tr.send(jreq('notifications/initialized'))
135
143
  return self
136
144
 
@@ -138,23 +146,23 @@ class Gateway:
138
146
  async def call(self, name, **args): return await self.rpc('tools/call', name=name, arguments=args)
139
147
 
140
148
  async def text(self, name, **args):
141
- "A tool call's text blocks joined; raises on `isError`"
149
+ "Join a tool reply's text blocks. Raise `RuntimeError` for `isError` replies."
142
150
  r = await self.call(name, **args)
143
151
  t = ''.join(c.get('text','') for c in r['content'] if c['type'] == 'text')
144
152
  if r.get('isError'): raise RuntimeError(t)
145
153
  return t
146
154
 
147
155
  async def aclose(self):
148
- "End the MCP session — the gateway stops the kernels this session created with autoclose — and drop the connection"
149
- await self.tr.delete()
150
- await self.tr.aclose()
156
+ "Request session termination and close the HTTP connection."
157
+ try: await self.tr.delete()
158
+ finally: await self.tr.aclose()
151
159
 
152
160
  # %% ../nbs/00_core.ipynb #0d054375
153
161
  async def default_gateway(
154
162
  cfgdir=None, # Config dir for `session_defaults` (the standard one if None)
155
163
  quiet=False, # Keep startup output out of replies?
156
164
  ):
157
- "An initialized `Gateway` on the default local gateway, plus the owned child rustygate when none was running (else None)"
165
+ "Return an initialized `Gateway` and its new child process, or None if it reused a gateway. The caller must stop any child."
158
166
  url, token, verify = resolve('', cfgdir)
159
167
  d = session_defaults(cfgdir, quiet)
160
168
  try: return await Gateway(url, token, verify).initialize(d), None
@@ -1,6 +1,10 @@
1
- """The MCP frontend: a stdio router over rustygate gateways
1
+ """Route stdio MCP requests to rustygate gateways
2
2
 
3
- The frontend Claude Code launches per conversation. `Router` speaks stdio MCP to the harness and forwards to gateways: rustygate serves the tool surface itself, so the router defines no tools — it fetches the local gateway's `tools/list`, adds a `host` parameter to the kernel-selection tools, and forwards `tools/call` verbatim to the right gateway, ids intact so cancellation maps through. `main` serves a `Router` on stdio and ends every gateway session on the way out — the DELETE that stops each session's autoclose kernels, and the owned child gateway with them.
3
+ Claude Code starts `clikernel-mcp` for each conversation. Its `Router` accepts MCP messages over stdio and sends tool calls to rustygate gateways. Rustygate implements the tools. The router gets their schemas from the local gateway and adds `host` to `list_kernels`, `use_kernel`, and `create`.
4
+
5
+ Each host has a separate gateway session. The router remembers one current host for calls without an explicit `host`. It removes `host` before forwarding a call but preserves the JSON-RPC request id. Cancellation notifications also retain their original ids.
6
+
7
+ `main` runs the router and closes its gateway sessions on exit. Closing a session sends an HTTP DELETE. The gateway then stops the kernels that session created with autoclose. If the router started a child gateway, it stops that process too.
4
8
 
5
9
  Docs: https://AnswerDotAI.github.io/clikernel/mcp.html.md"""
6
10
 
@@ -23,7 +27,7 @@ HOST_PARAM = {'type': 'string', 'description': 'Gateway to target: a gateways.to
23
27
  HOSTED = ('list_kernels', 'use_kernel', 'create')
24
28
 
25
29
  class Router:
26
- "Forward the harness's stdio MCP to rustygate gateways: one `Gateway` session per host, one current"
30
+ "Route stdio MCP to rustygate with one session per host and one current host."
27
31
  def __init__(self,
28
32
  cfgdir=None, # Config dir for `session_defaults` and `gateways.toml` (the standard one if None)
29
33
  quiet=False, # Keep startup output out of replies?
@@ -31,7 +35,7 @@ class Router:
31
35
  self.cfgdir,self.quiet,self.sessions,self.cur,self.child = cfgdir,quiet,{},'',None
32
36
 
33
37
  async def session(self, host=''):
34
- "The initialized `Gateway` for `host`, made on first use; empty means the default local gateway"
38
+ "Return the session for `host`, initializing it on first use. Empty `host` selects the default local gateway."
35
39
  if host not in self.sessions:
36
40
  if host:
37
41
  url, token, verify = resolve(host, self.cfgdir)
@@ -40,15 +44,18 @@ class Router:
40
44
  return self.sessions[host]
41
45
 
42
46
  async def aclose(self):
43
- "End every gateway session — stopping each one's autoclose kernels — then the owned child gateway"
44
- for s in self.sessions.values(): await s.aclose()
45
- if self.child: self.child.stop()
47
+ "Close all sessions and their autoclose kernels, then stop any owned gateway."
48
+ try: results = await asyncio.gather(*(s.aclose() for s in self.sessions.values()), return_exceptions=True)
49
+ finally:
50
+ if self.child: self.child.stop()
51
+ errors = [r for r in results if isinstance(r, BaseException)]
52
+ if errors: raise BaseExceptionGroup('MCP session cleanup failed', errors)
46
53
 
47
54
 
48
55
  # %% ../nbs/01_mcp.ipynb #79da6a3e
49
56
  @patch
50
57
  async def tools(self:Router):
51
- "The local gateway's tools, with `host` added to the kernel-selection tools"
58
+ "Return the local gateway's tools with `host` added to `list_kernels`, `use_kernel`, and `create`."
52
59
  ts = await (await self.session()).tools()
53
60
  for t in ts:
54
61
  if t['name'] in HOSTED: t['inputSchema'].setdefault('properties', {})['host'] = dict(HOST_PARAM)
@@ -56,7 +63,7 @@ async def tools(self:Router):
56
63
 
57
64
  @patch
58
65
  async def dispatch(self:Router, msg, requester=None):
59
- "One JSON-RPC message from the harness: initialize and ping answered here, the tool surface forwarded"
66
+ "Answer initialization and ping locally. Forward tool calls to gateways."
60
67
  method,id = msg.get('method'), msg.get('id')
61
68
  try:
62
69
  if method == 'initialize':
@@ -83,7 +90,7 @@ async def dispatch(self:Router, msg, requester=None):
83
90
  def main(
84
91
  quiet:store_true=False, # Keep startup output out of replies
85
92
  ):
86
- "The `clikernel-mcp` console script: the router on stdio"
93
+ "Run `clikernel-mcp` as a stdio server."
87
94
  async def _main():
88
95
  router = Router(quiet=quiet)
89
96
  try: await serve_stdio(router)
@@ -14,6 +14,12 @@ Remote gateways are the same tools with a `host` argument on `list_kernels`, `us
14
14
 
15
15
  # Working in it
16
16
 
17
+ For native Luau work use `lua(code=...)`, or `create(dlgname, language="luau")` for a named kernel. The first `lua` auto-starts Luau when no kernel is selected, with the same autoclose rules as Python. There is one current kernel: `py` requires Python and `lua` requires Luau, so a mismatch errors rather than switching. Use `use_kernel` or `create` to select deliberately. Omit `language` to reuse an existing binding unchanged or default a new one to Python; an explicit language must match an existing binding. A `dlgname` execution override changes only that call. These rules also apply on named remote gateways.
18
+
19
+ Start native work with `lua(code="help()")` for the bundled guide and examples, or `lua(code='help("ex.edit_file")')` for function details.
20
+
21
+ Luau has persistent globals, cell-local `local` variables, and native `rg.search`, `rg.find`, `fs.read_text`, `ex.edit_text`, `ex.view_file`/`ex.edit_file`, `ex.view_cell`/`ex.edit_cell`, `os.execute`, and `io.popen` APIs. File edits use arrays of command fields (like Python exhash tuples); `{inplace=false}` previews. Shell commands use `/bin/sh -c`; pipes support read/lines/write/flush/close and persist until closed. Interrupts terminate subprocess groups and close outstanding pipes. Python startup/inspectors and IPython magics do not apply to Luau. The following guidance is for Python:
22
+
17
23
  - Magics work as written, including `%%bash` for shell work. `%cd` expands `~` and is the way to change directory: prefer it over `os.chdir`.
18
24
  - Only the last expression in a cell displays. `print(...)` any earlier value you need to see.
19
25
  - Everything a cell outputs lands in the conversation. Be selective: `len(v)` first, then decide what to show.
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: clikernel
3
+ Version: 0.2.11
4
+ Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
5
+ Author: clikernel contributors
6
+ License: Apache-2.0
7
+ Project-URL: Repository, https://github.com/AnswerDotAI/clikernel
8
+ Project-URL: Documentation, https://AnswerDotAI.github.io/clikernel/
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: fastcore>=2.2.2
15
+ Requires-Dist: jupyasyncclient>=0.2.10
16
+ Requires-Dist: jupywire>=0.1.9
17
+ Requires-Dist: mcpmini>=0.0.5
18
+ Requires-Dist: rustygate>=0.1.11
19
+ Requires-Dist: httpx
20
+ Provides-Extra: dev
21
+ Requires-Dist: fastship; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # clikernel
25
+
26
+
27
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
28
+
29
+ `clikernel` gives an LLM agent a persistent Python or Luau session. Imports, variables, and results remain available between tool calls. An agent can create a kernel or attach to an existing one, including a user’s live solveit kernel, on a local or named remote gateway.
30
+
31
+ [rustygate](https://github.com/AnswerDotAI/rustygate) hosts the Jupyter kernels and provides their MCP tools. `clikernel` is the conversation-side router: the MCP host launches it over stdio, and it forwards requests to gateways over HTTP. It selects gateways from `gateways.toml`, supplies `startup.py` and `inspectors.py` to Python kernels it creates, and starts a private local gateway when needed. The default kernel is [ipymini](https://github.com/AnswerDotAI/ipymini).
32
+
33
+ Kernel ownership determines what happens when the conversation ends:
34
+
35
+ - A session closes kernels it created with `autoclose`, including the automatic kernel used by bare `py` or `lua` and kernels created with `create`’s default settings.
36
+ - Attaching with `use_kernel` does not make that session responsible for closing the kernel.
37
+ - A kernel created with `autoclose=false` on a resident gateway can outlive the conversation. A later conversation can attach and continue using its state.
38
+ - A private child gateway ends with its conversation. Use a resident gateway for kernels that need to survive across conversations.
39
+
40
+ ## Install
41
+
42
+ ``` sh
43
+ pip install clikernel
44
+ ```
45
+
46
+ This installs rustygate and ipymini. No service setup is required for a conversation-local kernel: clikernel starts a private gateway if it cannot find one. To retain kernels across conversations, run a resident gateway, for example through launchd or systemd:
47
+
48
+ ``` sh
49
+ rustygate --port 8787
50
+ ```
51
+
52
+ ## Use with an MCP host
53
+
54
+ Register the stdio server with your MCP host. For Claude Code:
55
+
56
+ ``` sh
57
+ claude mcp add clikernel -- clikernel-mcp
58
+ ```
59
+
60
+ Use `py(code=...)` for Python/IPython or `lua(code=...)` for bundled Luau. Either starts its language’s kernel when none is current. There is one current kernel: a language mismatch errors without switching or running the code. IPython magics, including `%%bash`, are Python-only.
61
+
62
+ For a named kernel, use `create(dlgname="work", language="luau")`. Omit `language` to reuse an existing binding unchanged, or default a new kernel to Python. An explicit language must match an existing binding. The other tools are `list_kernels`, `use_kernel`, `delete_kernel`, `restart`, and `interrupt`; creation, selection, and listing report the language.
63
+
64
+ These tools forward to rustygate. `list_kernels`, `use_kernel`, and `create` also accept a `host` naming a gateway from `gateways.toml`. One MCP registration can therefore reach multiple machines. Replies retain the gateway’s text and image blocks. Python startup and inspectors run only in Python, never Luau, including after restart.
65
+
66
+ `$CLIKERNEL_HOST` overrides the default gateway URL, `http://127.0.0.1:8787`. If no gateway answers, clikernel uses a private child gateway for the conversation. The ownership rules above determine which kernels close at session end.
67
+
68
+ Pass `--quiet`, as in `clikernel-mcp --quiet`, to omit startup output from replies. Python startup code still runs.
69
+
70
+ ## Configuration
71
+
72
+ Three optional files in `$XDG_CONFIG_HOME/clikernel/` configure the router, usually under `~/.config/clikernel/`:
73
+
74
+ - `startup.py` runs in each Python kernel clikernel creates, with `__file__` set to its path. Its output appears in the reply announcing the kernel unless `--quiet` is set.
75
+ - `inspectors.py` installs Python cell inspectors after startup. Define `inspect`, a list named `inspectors`, or both. Each inspector runs once before a cell: a one-argument inspector takes the cell’s AST, and a two-argument inspector takes the AST and raw source. Return a string to print a note before the output. Raise the provided `RuleBlock` to block execution. Other exceptions produce a warning and allow the cell to run. See [examples/inspectors.py](examples/inspectors.py).
76
+ - `gateways.toml` names remote gateways and configures authentication without putting tokens in tool arguments:
77
+
78
+ ``` toml
79
+ [gateways.solveit]
80
+ url = "https://solveit.example.com/gate"
81
+ token_env = "SOLVEIT_TOKEN"
82
+ verify = false # optional: accept a self-signed certificate
83
+ ```
84
+
85
+ ## The stream protocol
86
+
87
+ Run `clikernel` as a plain CLI process for clients that read a text stream rather than MCP messages. It uses a delimiter-framed stdin/stdout protocol:
88
+
89
+ - Input is not echoed.
90
+ - Each request gets a `.` acknowledgement.
91
+ - A per-process random delimiter marks the end of each response.
92
+ - Multiline cells are framed by `--` and the delimiter.
93
+
94
+ The startup banner supplies the protocol instructions and delimiter. Running `clikernel` without arguments creates a kernel and stops it on exit. `--kernel <id>` attaches to an existing kernel and leaves it running on exit.
@@ -1,8 +1,8 @@
1
1
  fastcore>=2.2.2
2
2
  jupyasyncclient>=0.2.10
3
3
  jupywire>=0.1.9
4
- mcpmini>=0.0.4
5
- rustygate>=0.1.7
4
+ mcpmini>=0.0.5
5
+ rustygate>=0.1.11
6
6
  httpx
7
7
 
8
8
  [dev]
@@ -19,8 +19,8 @@ dependencies = [
19
19
  "fastcore>=2.2.2",
20
20
  "jupyasyncclient>=0.2.10",
21
21
  "jupywire>=0.1.9",
22
- "mcpmini>=0.0.4",
23
- "rustygate>=0.1.7",
22
+ "mcpmini>=0.0.5",
23
+ "rustygate>=0.1.11",
24
24
  "httpx",
25
25
  ]
26
26
 
clikernel-0.2.9/PKG-INFO DELETED
@@ -1,76 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: clikernel
3
- Version: 0.2.9
4
- Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
5
- Author: clikernel contributors
6
- License: Apache-2.0
7
- Project-URL: Repository, https://github.com/AnswerDotAI/clikernel
8
- Project-URL: Documentation, https://AnswerDotAI.github.io/clikernel/
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3 :: Only
11
- Requires-Python: >=3.11
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: fastcore>=2.2.2
15
- Requires-Dist: jupyasyncclient>=0.2.10
16
- Requires-Dist: jupywire>=0.1.9
17
- Requires-Dist: mcpmini>=0.0.4
18
- Requires-Dist: rustygate>=0.1.7
19
- Requires-Dist: httpx
20
- Provides-Extra: dev
21
- Requires-Dist: fastship; extra == "dev"
22
- Dynamic: license-file
23
-
24
- # clikernel
25
-
26
-
27
- <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
28
-
29
- `clikernel` gives an LLM agent a persistent Python workbench built from two processes. A gateway ([rustygate](https://github.com/AnswerDotAI/rustygate)) hosts real Jupyter kernels ([ipymini](https://github.com/AnswerDotAI/ipymini) by default) and serves the MCP tool surface itself; kernels live there and persist until explicitly stopped. `clikernel` starts and stops with each conversation: a router the MCP host launches, speaking stdio MCP to the model and forwarding to gateways over HTTP. It adds what a single fixed endpoint cannot: gateway naming from `gateways.toml` (a `host` argument on the kernel-selection tools reaches any machine you’ve named), delivery of your `startup.py` and `inspectors.py` into every kernel a conversation creates, and a local gateway that always exists — found running, or started as a child that lives exactly as long as the conversation.
30
-
31
- Kernel scope is the gateway’s rule, one rule everywhere: a session’s end stops the kernels it created with autoclose (the bare-`py` auto kernel, and `create`’s default) and nothing else. A kernel created with `autoclose=false` on a persistent gateway outlives the conversation, and a later conversation reattaches with `use_kernel` and finds its state intact — including the user’s live solveit kernel.
32
-
33
- ## Install
34
-
35
- ``` sh
36
- pip install clikernel
37
- ```
38
-
39
- This brings [rustygate](https://github.com/AnswerDotAI/rustygate) and a kernel ([ipymini](https://github.com/AnswerDotAI/ipymini)) with it, and no service setup is needed: a conversation that finds no gateway starts its own. Run a resident gateway when kernels should outlive conversations (e.g. via launchd/systemd):
40
-
41
- ``` sh
42
- rustygate --port 8787
43
- ```
44
-
45
- ## Use with an MCP host
46
-
47
- Register the stdio server with your MCP host, e.g. for Claude Code:
48
-
49
- ``` sh
50
- claude mcp add clikernel -- clikernel-mcp
51
- ```
52
-
53
- The tools are rustygate’s, forwarded: `py` (the normal tool — it auto-starts a kernel when none is current, and magics like a `%%bash` first line run as written), `list_kernels`, `create`, `use_kernel`, `delete_kernel`, `restart`, and `interrupt`. The router adds one thing to them: `list_kernels`, `use_kernel`, and `create` take a `host` naming a gateway from `gateways.toml`, so one MCP entry reaches every machine you’ve named. Replies carry text and image blocks exactly as the gateway rendered them.
54
-
55
- A conversation cleans up after itself: its end stops the auto kernel and every `create` it made, unless `autoclose=false` asked for a keeper. Kernels reached with `use_kernel` are never touched. `$CLIKERNEL_HOST` overrides the default gateway (`http://127.0.0.1:8787`), and when nothing answers there, the conversation runs on a private child gateway that ends with it.
56
-
57
- Pass `--quiet` (`clikernel-mcp --quiet`) for a host whose sessions should not see banner noise: startup still runs, but its output stays out of every reply.
58
-
59
- ## Configuration
60
-
61
- Three optional files in `$XDG_CONFIG_HOME/clikernel/` (usually `~/.config/clikernel/`):
62
-
63
- - `startup.py` — run in every kernel clikernel creates, with `__file__` bound to its path; its output returns in the reply that announces the kernel.
64
- - `inspectors.py` — cell inspectors installed after startup. The file may define `inspect` and/or a list `inspectors`; each is called once per cell before it runs (1-arg: the cell’s AST; 2-arg: AST and raw source). Return a string to print a note before the cell’s output, raise `RuleBlock` (provided in the namespace) to block the cell; any other exception warns and the cell runs. See `examples/inspectors.py`.
65
- - `gateways.toml` — named remote gateways, so tokens never appear in tool arguments:
66
-
67
- ``` toml
68
- [gateways.solveit]
69
- url = "https://solveit.example.com/gate"
70
- token_env = "SOLVEIT_TOKEN"
71
- verify = false # optional: accept a self-signed certificate
72
- ```
73
-
74
- ## The stream protocol
75
-
76
- Run `clikernel` as a plain CLI process and the same client speaks a delimiter-framed stdin/stdout protocol for token-reading clients: no echo, a cheap `.` acknowledgement per request, responses ended by a per-process random delimiter, multiline cells framed by `--` and the delimiter. The full recipe is announced in the process’s own startup banner. Run bare it creates a kernel and stops it on exit; `--kernel <id>` attaches to an existing kernel and leaves it as found.
clikernel-0.2.9/README.md DELETED
@@ -1,53 +0,0 @@
1
- # clikernel
2
-
3
-
4
- <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
5
-
6
- `clikernel` gives an LLM agent a persistent Python workbench built from two processes. A gateway ([rustygate](https://github.com/AnswerDotAI/rustygate)) hosts real Jupyter kernels ([ipymini](https://github.com/AnswerDotAI/ipymini) by default) and serves the MCP tool surface itself; kernels live there and persist until explicitly stopped. `clikernel` starts and stops with each conversation: a router the MCP host launches, speaking stdio MCP to the model and forwarding to gateways over HTTP. It adds what a single fixed endpoint cannot: gateway naming from `gateways.toml` (a `host` argument on the kernel-selection tools reaches any machine you’ve named), delivery of your `startup.py` and `inspectors.py` into every kernel a conversation creates, and a local gateway that always exists — found running, or started as a child that lives exactly as long as the conversation.
7
-
8
- Kernel scope is the gateway’s rule, one rule everywhere: a session’s end stops the kernels it created with autoclose (the bare-`py` auto kernel, and `create`’s default) and nothing else. A kernel created with `autoclose=false` on a persistent gateway outlives the conversation, and a later conversation reattaches with `use_kernel` and finds its state intact — including the user’s live solveit kernel.
9
-
10
- ## Install
11
-
12
- ``` sh
13
- pip install clikernel
14
- ```
15
-
16
- This brings [rustygate](https://github.com/AnswerDotAI/rustygate) and a kernel ([ipymini](https://github.com/AnswerDotAI/ipymini)) with it, and no service setup is needed: a conversation that finds no gateway starts its own. Run a resident gateway when kernels should outlive conversations (e.g. via launchd/systemd):
17
-
18
- ``` sh
19
- rustygate --port 8787
20
- ```
21
-
22
- ## Use with an MCP host
23
-
24
- Register the stdio server with your MCP host, e.g. for Claude Code:
25
-
26
- ``` sh
27
- claude mcp add clikernel -- clikernel-mcp
28
- ```
29
-
30
- The tools are rustygate’s, forwarded: `py` (the normal tool — it auto-starts a kernel when none is current, and magics like a `%%bash` first line run as written), `list_kernels`, `create`, `use_kernel`, `delete_kernel`, `restart`, and `interrupt`. The router adds one thing to them: `list_kernels`, `use_kernel`, and `create` take a `host` naming a gateway from `gateways.toml`, so one MCP entry reaches every machine you’ve named. Replies carry text and image blocks exactly as the gateway rendered them.
31
-
32
- A conversation cleans up after itself: its end stops the auto kernel and every `create` it made, unless `autoclose=false` asked for a keeper. Kernels reached with `use_kernel` are never touched. `$CLIKERNEL_HOST` overrides the default gateway (`http://127.0.0.1:8787`), and when nothing answers there, the conversation runs on a private child gateway that ends with it.
33
-
34
- Pass `--quiet` (`clikernel-mcp --quiet`) for a host whose sessions should not see banner noise: startup still runs, but its output stays out of every reply.
35
-
36
- ## Configuration
37
-
38
- Three optional files in `$XDG_CONFIG_HOME/clikernel/` (usually `~/.config/clikernel/`):
39
-
40
- - `startup.py` — run in every kernel clikernel creates, with `__file__` bound to its path; its output returns in the reply that announces the kernel.
41
- - `inspectors.py` — cell inspectors installed after startup. The file may define `inspect` and/or a list `inspectors`; each is called once per cell before it runs (1-arg: the cell’s AST; 2-arg: AST and raw source). Return a string to print a note before the cell’s output, raise `RuleBlock` (provided in the namespace) to block the cell; any other exception warns and the cell runs. See `examples/inspectors.py`.
42
- - `gateways.toml` — named remote gateways, so tokens never appear in tool arguments:
43
-
44
- ``` toml
45
- [gateways.solveit]
46
- url = "https://solveit.example.com/gate"
47
- token_env = "SOLVEIT_TOKEN"
48
- verify = false # optional: accept a self-signed certificate
49
- ```
50
-
51
- ## The stream protocol
52
-
53
- Run `clikernel` as a plain CLI process and the same client speaks a delimiter-framed stdin/stdout protocol for token-reading clients: no echo, a cheap `.` acknowledgement per request, responses ended by a per-process random delimiter, multiline cells framed by `--` and the delimiter. The full recipe is announced in the process’s own startup banner. Run bare it creates a kernel and stops it on exit; `--kernel <id>` attaches to an existing kernel and leaves it as found.
@@ -1,76 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: clikernel
3
- Version: 0.2.9
4
- Summary: Serve persistent Jupyter kernels to LLMs as concise text, over MCP or a plain stream protocol
5
- Author: clikernel contributors
6
- License: Apache-2.0
7
- Project-URL: Repository, https://github.com/AnswerDotAI/clikernel
8
- Project-URL: Documentation, https://AnswerDotAI.github.io/clikernel/
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3 :: Only
11
- Requires-Python: >=3.11
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Requires-Dist: fastcore>=2.2.2
15
- Requires-Dist: jupyasyncclient>=0.2.10
16
- Requires-Dist: jupywire>=0.1.9
17
- Requires-Dist: mcpmini>=0.0.4
18
- Requires-Dist: rustygate>=0.1.7
19
- Requires-Dist: httpx
20
- Provides-Extra: dev
21
- Requires-Dist: fastship; extra == "dev"
22
- Dynamic: license-file
23
-
24
- # clikernel
25
-
26
-
27
- <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
28
-
29
- `clikernel` gives an LLM agent a persistent Python workbench built from two processes. A gateway ([rustygate](https://github.com/AnswerDotAI/rustygate)) hosts real Jupyter kernels ([ipymini](https://github.com/AnswerDotAI/ipymini) by default) and serves the MCP tool surface itself; kernels live there and persist until explicitly stopped. `clikernel` starts and stops with each conversation: a router the MCP host launches, speaking stdio MCP to the model and forwarding to gateways over HTTP. It adds what a single fixed endpoint cannot: gateway naming from `gateways.toml` (a `host` argument on the kernel-selection tools reaches any machine you’ve named), delivery of your `startup.py` and `inspectors.py` into every kernel a conversation creates, and a local gateway that always exists — found running, or started as a child that lives exactly as long as the conversation.
30
-
31
- Kernel scope is the gateway’s rule, one rule everywhere: a session’s end stops the kernels it created with autoclose (the bare-`py` auto kernel, and `create`’s default) and nothing else. A kernel created with `autoclose=false` on a persistent gateway outlives the conversation, and a later conversation reattaches with `use_kernel` and finds its state intact — including the user’s live solveit kernel.
32
-
33
- ## Install
34
-
35
- ``` sh
36
- pip install clikernel
37
- ```
38
-
39
- This brings [rustygate](https://github.com/AnswerDotAI/rustygate) and a kernel ([ipymini](https://github.com/AnswerDotAI/ipymini)) with it, and no service setup is needed: a conversation that finds no gateway starts its own. Run a resident gateway when kernels should outlive conversations (e.g. via launchd/systemd):
40
-
41
- ``` sh
42
- rustygate --port 8787
43
- ```
44
-
45
- ## Use with an MCP host
46
-
47
- Register the stdio server with your MCP host, e.g. for Claude Code:
48
-
49
- ``` sh
50
- claude mcp add clikernel -- clikernel-mcp
51
- ```
52
-
53
- The tools are rustygate’s, forwarded: `py` (the normal tool — it auto-starts a kernel when none is current, and magics like a `%%bash` first line run as written), `list_kernels`, `create`, `use_kernel`, `delete_kernel`, `restart`, and `interrupt`. The router adds one thing to them: `list_kernels`, `use_kernel`, and `create` take a `host` naming a gateway from `gateways.toml`, so one MCP entry reaches every machine you’ve named. Replies carry text and image blocks exactly as the gateway rendered them.
54
-
55
- A conversation cleans up after itself: its end stops the auto kernel and every `create` it made, unless `autoclose=false` asked for a keeper. Kernels reached with `use_kernel` are never touched. `$CLIKERNEL_HOST` overrides the default gateway (`http://127.0.0.1:8787`), and when nothing answers there, the conversation runs on a private child gateway that ends with it.
56
-
57
- Pass `--quiet` (`clikernel-mcp --quiet`) for a host whose sessions should not see banner noise: startup still runs, but its output stays out of every reply.
58
-
59
- ## Configuration
60
-
61
- Three optional files in `$XDG_CONFIG_HOME/clikernel/` (usually `~/.config/clikernel/`):
62
-
63
- - `startup.py` — run in every kernel clikernel creates, with `__file__` bound to its path; its output returns in the reply that announces the kernel.
64
- - `inspectors.py` — cell inspectors installed after startup. The file may define `inspect` and/or a list `inspectors`; each is called once per cell before it runs (1-arg: the cell’s AST; 2-arg: AST and raw source). Return a string to print a note before the cell’s output, raise `RuleBlock` (provided in the namespace) to block the cell; any other exception warns and the cell runs. See `examples/inspectors.py`.
65
- - `gateways.toml` — named remote gateways, so tokens never appear in tool arguments:
66
-
67
- ``` toml
68
- [gateways.solveit]
69
- url = "https://solveit.example.com/gate"
70
- token_env = "SOLVEIT_TOKEN"
71
- verify = false # optional: accept a self-signed certificate
72
- ```
73
-
74
- ## The stream protocol
75
-
76
- Run `clikernel` as a plain CLI process and the same client speaks a delimiter-framed stdin/stdout protocol for token-reading clients: no echo, a cheap `.` acknowledgement per request, responses ended by a per-process random delimiter, multiline cells framed by `--` and the delimiter. The full recipe is announced in the process’s own startup banner. Run bare it creates a kernel and stops it on exit; `--kernel <id>` attaches to an existing kernel and leaves it as found.
File without changes
File without changes
File without changes