insightfactory-cli 1.1.2.dev30__py3-none-any.whl → 1.1.2.dev31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- if_cli/commands/mcp.py +4 -1
- if_cli/router/log.py +9 -2
- if_cli/router/server.py +24 -1
- {insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/METADATA +9 -1
- {insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/RECORD +8 -8
- {insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/WHEEL +0 -0
- {insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/entry_points.txt +0 -0
- {insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/licenses/LICENSE +0 -0
if_cli/commands/mcp.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import importlib.util
|
|
4
|
+
import platform
|
|
4
5
|
import sys
|
|
5
6
|
|
|
6
7
|
from if_cli.cli import Options, parse_args
|
|
7
8
|
from if_cli.config import config_file, get_profile, list_factories, load_config, parse_factory_section
|
|
8
9
|
from if_cli.http import parse_timeout_seconds
|
|
10
|
+
from if_cli.router.log import log
|
|
9
11
|
from if_cli.runtime import die
|
|
10
12
|
|
|
11
13
|
MCP_OPTIONS: Options = {
|
|
@@ -75,6 +77,8 @@ def _stray_positional(rest: list[str]) -> str | None:
|
|
|
75
77
|
|
|
76
78
|
|
|
77
79
|
def mcp_command(argv: list[str]) -> None:
|
|
80
|
+
log(f"starting on Python {platform.python_version()}")
|
|
81
|
+
|
|
78
82
|
if "-h" in argv or "--help" in argv:
|
|
79
83
|
sys.stdout.write(MCP_USAGE)
|
|
80
84
|
return
|
|
@@ -146,7 +150,6 @@ def mcp_command(argv: list[str]) -> None:
|
|
|
146
150
|
# import bug inside if_cli.router propagates instead of reading as a missing extra.
|
|
147
151
|
if importlib.util.find_spec("mcp") is None:
|
|
148
152
|
die(MISSING_MCP_EXTRA)
|
|
149
|
-
from if_cli.router.log import log
|
|
150
153
|
from if_cli.router.server import build_router, run_server
|
|
151
154
|
|
|
152
155
|
timeout = parse_timeout_seconds(values["timeout"], "--timeout")
|
if_cli/router/log.py
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
# Captured at import: this module loads before the router does anything else
|
|
7
|
+
# diagnosable, so it stands in for process start. Two `log()` lines can then
|
|
8
|
+
# be subtracted to time the phase between them.
|
|
9
|
+
_START = time.monotonic()
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
def log(message: str) -> None:
|
|
7
|
-
"""Write one diagnostic line to stderr.
|
|
13
|
+
"""Write one timestamped diagnostic line to stderr.
|
|
8
14
|
|
|
9
15
|
The router speaks MCP over stdout, so stderr is the only channel that can
|
|
10
16
|
carry anything to the operator. This lives in its own module because both
|
|
11
17
|
`server` and `upstream` write to it and `server` imports `upstream`.
|
|
12
18
|
"""
|
|
13
|
-
|
|
19
|
+
elapsed_ms = round((time.monotonic() - _START) * 1000)
|
|
20
|
+
sys.stderr.write(f"[if-cli mcp] +{elapsed_ms}ms {message}\n")
|
if_cli/router/server.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import Any, TypeVar
|
|
|
9
9
|
import anyio
|
|
10
10
|
import mcp_types as types
|
|
11
11
|
from mcp.server import Server
|
|
12
|
-
from mcp.server.context import ServerRequestContext
|
|
12
|
+
from mcp.server.context import CallNext, HandlerResult, ServerMiddleware, ServerRequestContext
|
|
13
13
|
from mcp.server.stdio import stdio_server
|
|
14
14
|
from mcp.shared.exceptions import MCPError
|
|
15
15
|
|
|
@@ -137,6 +137,7 @@ class Router:
|
|
|
137
137
|
|
|
138
138
|
async def build_catalog(self) -> list[types.Tool]:
|
|
139
139
|
"""Build the merged catalogue. Safe to call again to pick up a new login."""
|
|
140
|
+
log(f"catalogue: building from {self.config.catalog_source}")
|
|
140
141
|
catalogues = await self._catalogues()
|
|
141
142
|
source = catalogues.get(self.config.catalog_source)
|
|
142
143
|
if source is None:
|
|
@@ -536,6 +537,27 @@ def advertised_extensions(router: Router) -> dict[str, dict[str, Any]]:
|
|
|
536
537
|
return {SKILLS_EXTENSION: {}} if router.config.skills else {}
|
|
537
538
|
|
|
538
539
|
|
|
540
|
+
def _log_first_initialize() -> ServerMiddleware[Any]:
|
|
541
|
+
"""Log once the router has answered a client's `initialize`.
|
|
542
|
+
|
|
543
|
+
Read against `prime_catalog`'s startup log lines, this tells an operator
|
|
544
|
+
apart a slow process (nothing logged yet), a slow factory (catalogue
|
|
545
|
+
build still running past this point), and a client that called before
|
|
546
|
+
the handshake (a tool call logged before this line ever appears).
|
|
547
|
+
"""
|
|
548
|
+
logged = False
|
|
549
|
+
|
|
550
|
+
async def middleware(ctx: ServerRequestContext[Any, Any], call_next: CallNext) -> HandlerResult:
|
|
551
|
+
nonlocal logged
|
|
552
|
+
result = await call_next(ctx)
|
|
553
|
+
if not logged and ctx.method == "initialize":
|
|
554
|
+
logged = True
|
|
555
|
+
log("answered the first initialize")
|
|
556
|
+
return result
|
|
557
|
+
|
|
558
|
+
return middleware
|
|
559
|
+
|
|
560
|
+
|
|
539
561
|
async def _serve(router: Router) -> None:
|
|
540
562
|
server: Server[Any] = Server(
|
|
541
563
|
SERVER_NAME,
|
|
@@ -553,6 +575,7 @@ async def _serve(router: Router) -> None:
|
|
|
553
575
|
# the handshake rebuilds its capabilities from `server.extensions` once the
|
|
554
576
|
# protocol version is known, and ignores the ones computed here.
|
|
555
577
|
server.extensions = advertised_extensions(router)
|
|
578
|
+
server.middleware.append(_log_first_initialize())
|
|
556
579
|
# Answer `initialize` immediately: the catalogue build runs as a background
|
|
557
580
|
# task so one hung factory reads as a slow tools/list, not a failed
|
|
558
581
|
# handshake (clients cap startup time).
|
{insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: insightfactory-cli
|
|
3
|
-
Version: 1.1.2.
|
|
3
|
+
Version: 1.1.2.dev31
|
|
4
4
|
Summary: Profile-based authentication CLI for the InsightFactory Interfaces API
|
|
5
5
|
Project-URL: Homepage, https://insightfactory.ai
|
|
6
6
|
Author-email: "insightfactory.ai Support" <support@insightfactory.ai>
|
|
@@ -265,6 +265,14 @@ uv tool install 'insightfactory-cli[mcp]'
|
|
|
265
265
|
uvx --from 'insightfactory-cli[mcp]' if-cli mcp foundry
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
+
The first `uvx` launch resolves and downloads the package from PyPI before it runs, which
|
|
269
|
+
added roughly four to six seconds on top of the usual startup in local testing, against
|
|
270
|
+
under two seconds once `uv`'s cache was warm. A client with a fixed handshake budget
|
|
271
|
+
(Claude Code allows 30 seconds) usually has room for that, but a slow network can close
|
|
272
|
+
the gap. Two ways to avoid paying it on every launch: `uv tool install 'insightfactory-cli[mcp]'`
|
|
273
|
+
puts a real script on `PATH` with no resolve step, or run the same `uvx --from ...` command
|
|
274
|
+
by hand once before pointing a client at it, which warms `uv`'s cache for the launches after.
|
|
275
|
+
|
|
268
276
|
Add it to `.mcp.json`:
|
|
269
277
|
|
|
270
278
|
```json
|
{insightfactory_cli-1.1.2.dev30.dist-info → insightfactory_cli-1.1.2.dev31.dist-info}/RECORD
RENAMED
|
@@ -17,19 +17,19 @@ if_cli/commands/api.py,sha256=XeVhhD39Oho4xNbMJIGh1I6vi53on5EGGsAtsb_5TAg,9016
|
|
|
17
17
|
if_cli/commands/config.py,sha256=1KAxjme897NIbXTGBw1rNJlXnFvhbEUf5MAli8A93ns,1724
|
|
18
18
|
if_cli/commands/login.py,sha256=vxsYcRZWsaNEzekyN1zy4ROJO-OCGUpjaVUBhtbK1LY,3255
|
|
19
19
|
if_cli/commands/logout.py,sha256=TW0P4_tXcTtiHHR73xVPHyhlbOWD1jBnkZsMImCiCBw,668
|
|
20
|
-
if_cli/commands/mcp.py,sha256=
|
|
20
|
+
if_cli/commands/mcp.py,sha256=iaSsqWkV1lnFjIw9y1pX1ORB1MdKiOVWgK23EDIsDB4,7342
|
|
21
21
|
if_cli/commands/profiles.py,sha256=tgZP5sMqzbFHXlfvglL2plsGdU0M3joo3wvQKX45J1U,3950
|
|
22
22
|
if_cli/commands/set_token.py,sha256=BVyv_QmByvllr04pE2sz8JFEscHbIgOC-7ZPg61PRoc,1886
|
|
23
23
|
if_cli/commands/token.py,sha256=CyiXkyH7vxgH91-n-XJzeHnLI6mZJwO9Js5AHvfHaeM,1120
|
|
24
24
|
if_cli/router/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
25
|
if_cli/router/catalog.py,sha256=Er2ZbWTwDUVU-qBGCvyA0bIlFk3UYw_6sVUlYwBhMpI,4992
|
|
26
26
|
if_cli/router/content.py,sha256=POL2n5NIY8yenQHJe8pUbTuTNpPSUSm0Y6Txbvehzhs,14487
|
|
27
|
-
if_cli/router/log.py,sha256=
|
|
27
|
+
if_cli/router/log.py,sha256=wtaqsFcD3hPJRu4jCJaDMSedj-43Y07ym62YgrOm1yM,740
|
|
28
28
|
if_cli/router/policy.py,sha256=CclbjuOA5mwwoQiNDa17iQeN9eXRbfy11bS9JFd8gL4,2840
|
|
29
|
-
if_cli/router/server.py,sha256=
|
|
29
|
+
if_cli/router/server.py,sha256=yCjGAm-tnZBOTg5AfvojnLcKcZLEbQQeT9iqqX8XV5A,27397
|
|
30
30
|
if_cli/router/upstream.py,sha256=Ga0OM918Y1CFyQFGs2wO0ocyjcMe1Wk0LvffOZOaE34,18559
|
|
31
|
-
insightfactory_cli-1.1.2.
|
|
32
|
-
insightfactory_cli-1.1.2.
|
|
33
|
-
insightfactory_cli-1.1.2.
|
|
34
|
-
insightfactory_cli-1.1.2.
|
|
35
|
-
insightfactory_cli-1.1.2.
|
|
31
|
+
insightfactory_cli-1.1.2.dev31.dist-info/METADATA,sha256=LkShsicB2QHUEjaS9FmsW9T2dPlGIl92Ew-OMsSCg28,24331
|
|
32
|
+
insightfactory_cli-1.1.2.dev31.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
33
|
+
insightfactory_cli-1.1.2.dev31.dist-info/entry_points.txt,sha256=UlX854D7f-7dBj5F0wGdakVy6vopvP9hA1BN-gv739w,44
|
|
34
|
+
insightfactory_cli-1.1.2.dev31.dist-info/licenses/LICENSE,sha256=8eZ1YAABL398qESVJc_FlK1voYQ0GAh-R_rNBQK6QH8,234
|
|
35
|
+
insightfactory_cli-1.1.2.dev31.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|