loki-mode 7.28.2 → 7.30.0
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.
- package/README.md +4 -3
- package/SKILL.md +8 -2
- package/VERSION +1 -1
- package/autonomy/loki +285 -26
- package/autonomy/mcp-launch.sh +282 -0
- package/autonomy/provider-offer.sh +249 -0
- package/autonomy/quickstart.sh +584 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +10 -1
- package/docs/competitive/emergence-others-analysis.md +1 -1
- package/docs/competitive/replit-lovable-analysis.md +2 -2
- package/loki-ts/dist/loki.js +208 -208
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +186 -35
- package/package.json +1 -1
- package/templates/simple-todo-app.md +3 -0
package/mcp/__init__.py
CHANGED
package/mcp/server.py
CHANGED
|
@@ -467,39 +467,156 @@ def _emit_context_relevance_signal(
|
|
|
467
467
|
thread.start()
|
|
468
468
|
|
|
469
469
|
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
#
|
|
473
|
-
|
|
470
|
+
# ============================================================
|
|
471
|
+
# Loading the pip MCP SDK's FastMCP under a NAMESPACE COLLISION
|
|
472
|
+
# ============================================================
|
|
473
|
+
#
|
|
474
|
+
# Root cause (task 562): this repo ships a local package named `mcp/`
|
|
475
|
+
# (this very file is mcp/server.py). That local package SHADOWS the
|
|
476
|
+
# pip-installed MCP SDK, which is also named `mcp`. The two cannot both
|
|
477
|
+
# own the top-level `mcp` name in one interpreter.
|
|
478
|
+
#
|
|
479
|
+
# The pre-task code tried to sidestep this by loading mcp/server/fastmcp.py
|
|
480
|
+
# directly from site-packages via importlib. That worked for the SDK's old
|
|
481
|
+
# single-FILE layout, but MCP SDK 1.x ships FastMCP as a PACKAGE DIRECTORY
|
|
482
|
+
# (mcp/server/fastmcp/__init__.py) whose own code does absolute imports like
|
|
483
|
+
# `from mcp.types import Icon` and `from mcp.server.lowlevel import Server`.
|
|
484
|
+
# Under shadowing those resolve to the LOCAL package and raise
|
|
485
|
+
# `ModuleNotFoundError: No module named 'mcp.types'`, so FastMCP never loads
|
|
486
|
+
# and the server exits. (lsp_proxy.py has the same latent failure but
|
|
487
|
+
# silently degrades to a no-op shim.)
|
|
488
|
+
#
|
|
489
|
+
# So the real fix is NOT file-vs-directory detection: it is resolving the
|
|
490
|
+
# namespace collision so the genuine SDK can import its own `mcp.*` subtree.
|
|
491
|
+
# We do this by temporarily letting the REAL SDK own the `mcp` name:
|
|
492
|
+
# 1. snapshot + evict the local `mcp` / `mcp.*` modules from sys.modules,
|
|
493
|
+
# 2. drop the repo root (and "" / ".") from sys.path so the next import of
|
|
494
|
+
# `mcp` resolves to site-packages, not the local package,
|
|
495
|
+
# 3. import the real `mcp.server.fastmcp` (and eagerly `mcp.types`), which
|
|
496
|
+
# transitively caches the real `mcp.*` subtree in sys.modules,
|
|
497
|
+
# 4. restore the LOCAL `mcp` and `mcp.server` entries so the rest of this
|
|
498
|
+
# codebase keeps using the local package for its own relative imports,
|
|
499
|
+
# while the real SDK submodules (mcp.types, mcp.shared.*,
|
|
500
|
+
# mcp.server.fastmcp.*, mcp.server.lowlevel.*) stay cached for the SDK's
|
|
501
|
+
# runtime use. FastMCP holds direct references to its dependencies once
|
|
502
|
+
# imported, so it does not re-resolve `mcp.server` by name at runtime.
|
|
503
|
+
#
|
|
504
|
+
# This is the least-invasive fix (it does not rename the local package, which
|
|
505
|
+
# mcp/__init__.py documents as intended behavior). It is inherently a bit
|
|
506
|
+
# fragile because it juggles sys.modules; the regression test for this code
|
|
507
|
+
# path is an END-TO-END one (start the server, complete an MCP stdio
|
|
508
|
+
# handshake, list tools), not a file-exists check, because only a real
|
|
509
|
+
# handshake proves FastMCP actually loaded and the subtree resolved.
|
|
510
|
+
import importlib
|
|
474
511
|
import site
|
|
475
512
|
|
|
476
|
-
_fastmcp_found = False
|
|
477
|
-
_search_paths = []
|
|
478
|
-
try:
|
|
479
|
-
_search_paths.extend(site.getsitepackages())
|
|
480
|
-
except AttributeError:
|
|
481
|
-
pass
|
|
482
|
-
try:
|
|
483
|
-
_search_paths.append(site.getusersitepackages())
|
|
484
|
-
except AttributeError:
|
|
485
|
-
pass
|
|
486
513
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
514
|
+
def _real_mcp_search_dirs():
|
|
515
|
+
"""Ordered site directories to search for the pip MCP SDK."""
|
|
516
|
+
dirs = []
|
|
517
|
+
try:
|
|
518
|
+
dirs.extend(site.getsitepackages())
|
|
519
|
+
except AttributeError:
|
|
520
|
+
pass
|
|
521
|
+
try:
|
|
522
|
+
dirs.append(site.getusersitepackages())
|
|
523
|
+
except AttributeError:
|
|
524
|
+
pass
|
|
525
|
+
return dirs
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _mcp_sdk_present(search_dirs=None):
|
|
529
|
+
"""True if the pip MCP SDK appears installed in any site dir, accepting
|
|
530
|
+
both the legacy single-file layout and the 1.x package-directory layout.
|
|
531
|
+
|
|
532
|
+
Pure filesystem probe (no import side effects), kept standalone so the
|
|
533
|
+
both-layouts behaviour can be unit-tested against mktemp fixture dirs.
|
|
534
|
+
"""
|
|
535
|
+
if search_dirs is None:
|
|
536
|
+
search_dirs = _real_mcp_search_dirs()
|
|
537
|
+
for _site_dir in search_dirs:
|
|
538
|
+
if not _site_dir:
|
|
539
|
+
continue
|
|
540
|
+
_file_layout = os.path.join(_site_dir, "mcp", "server", "fastmcp.py")
|
|
541
|
+
_pkg_layout = os.path.join(
|
|
542
|
+
_site_dir, "mcp", "server", "fastmcp", "__init__.py"
|
|
493
543
|
)
|
|
494
|
-
if
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
if
|
|
502
|
-
|
|
544
|
+
if os.path.isfile(_file_layout) or os.path.isfile(_pkg_layout):
|
|
545
|
+
return True
|
|
546
|
+
return False
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _load_real_fastmcp():
|
|
550
|
+
"""Import the genuine pip MCP SDK's FastMCP class, resolving the local-vs-
|
|
551
|
+
SDK `mcp` namespace collision. Returns the FastMCP class, or None if the
|
|
552
|
+
SDK cannot be loaded. Restores the local `mcp`/`mcp.server` modules before
|
|
553
|
+
returning so the rest of this module keeps working unchanged.
|
|
554
|
+
"""
|
|
555
|
+
if not _mcp_sdk_present():
|
|
556
|
+
return None
|
|
557
|
+
|
|
558
|
+
# 1. Snapshot every currently-loaded local `mcp`/`mcp.*` module so we can
|
|
559
|
+
# restore the ones this codebase depends on afterwards.
|
|
560
|
+
_saved_local = {
|
|
561
|
+
_k: _v for _k, _v in list(sys.modules.items())
|
|
562
|
+
if _k == "mcp" or _k.startswith("mcp.")
|
|
563
|
+
}
|
|
564
|
+
# 2. Evict them so the real SDK can claim the `mcp` name on import.
|
|
565
|
+
for _k in list(_saved_local):
|
|
566
|
+
del sys.modules[_k]
|
|
567
|
+
|
|
568
|
+
# 3. Drop repo-root / cwd entries from sys.path for the SDK import so
|
|
569
|
+
# `mcp` resolves to site-packages rather than the local package.
|
|
570
|
+
_repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
571
|
+
_saved_path = sys.path[:]
|
|
572
|
+
sys.path[:] = [
|
|
573
|
+
_p for _p in sys.path
|
|
574
|
+
if _p not in ("", ".")
|
|
575
|
+
and os.path.abspath(_p) != os.path.abspath(_repo_root)
|
|
576
|
+
]
|
|
577
|
+
|
|
578
|
+
_fastmcp_cls = None
|
|
579
|
+
try:
|
|
580
|
+
_real_fastmcp = importlib.import_module("mcp.server.fastmcp")
|
|
581
|
+
# Eagerly import the subtree FastMCP touches at runtime so the real
|
|
582
|
+
# modules are cached before we restore the local `mcp` over the name.
|
|
583
|
+
importlib.import_module("mcp.types")
|
|
584
|
+
importlib.import_module("mcp.server.lowlevel")
|
|
585
|
+
_fastmcp_cls = getattr(_real_fastmcp, "FastMCP", None)
|
|
586
|
+
except Exception as _exc: # pragma: no cover - defensive
|
|
587
|
+
logger.error("Failed to import the pip MCP SDK FastMCP: %s", _exc)
|
|
588
|
+
_fastmcp_cls = None
|
|
589
|
+
finally:
|
|
590
|
+
# 4. Restore sys.path and re-pin the LOCAL `mcp` + `mcp.server` modules
|
|
591
|
+
# so this codebase's own relative/absolute imports keep resolving
|
|
592
|
+
# locally. We intentionally leave the real `mcp.types`,
|
|
593
|
+
# `mcp.shared.*`, `mcp.server.fastmcp.*`, and `mcp.server.lowlevel.*`
|
|
594
|
+
# cached for the SDK's runtime use; the local package never defined
|
|
595
|
+
# those submodules, so there is nothing to clobber.
|
|
596
|
+
sys.path[:] = _saved_path
|
|
597
|
+
for _k in ("mcp", "mcp.server"):
|
|
598
|
+
if _k in _saved_local:
|
|
599
|
+
sys.modules[_k] = _saved_local[_k]
|
|
600
|
+
for _k, _v in _saved_local.items():
|
|
601
|
+
# Restore any other purely-local submodules that the SDK import did
|
|
602
|
+
# not legitimately replace (e.g. mcp.magic_tools, mcp.tools).
|
|
603
|
+
_real = sys.modules.get(_k)
|
|
604
|
+
if _real is None:
|
|
605
|
+
sys.modules[_k] = _v
|
|
606
|
+
|
|
607
|
+
return _fastmcp_cls
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
FastMCP = _load_real_fastmcp()
|
|
611
|
+
|
|
612
|
+
if FastMCP is None:
|
|
613
|
+
logger.error(
|
|
614
|
+
"MCP SDK (pip package 'mcp') not found or not importable. "
|
|
615
|
+
"Install it, then re-run. The simplest path is: 'loki mcp', which "
|
|
616
|
+
"creates a managed virtualenv at .loki/mcp-venv and installs "
|
|
617
|
+
"mcp/requirements.txt for you. To install manually: "
|
|
618
|
+
"pip install -r mcp/requirements.txt (or: pip install mcp)."
|
|
619
|
+
)
|
|
503
620
|
sys.exit(1)
|
|
504
621
|
|
|
505
622
|
# Read version from VERSION file instead of hardcoding
|
|
@@ -509,12 +626,31 @@ try:
|
|
|
509
626
|
except Exception:
|
|
510
627
|
_version = "unknown"
|
|
511
628
|
|
|
512
|
-
# Initialize FastMCP server
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
629
|
+
# Initialize FastMCP server.
|
|
630
|
+
#
|
|
631
|
+
# Task 562: pass only kwargs the installed SDK actually accepts. MCP SDK 1.x
|
|
632
|
+
# FastMCP.__init__ has no `version=`/`description=` parameters (it uses
|
|
633
|
+
# `instructions=`), so passing them raises TypeError and the server never
|
|
634
|
+
# starts. We introspect the signature and forward only supported optional
|
|
635
|
+
# kwargs, keeping forward/backward compatibility across SDK versions.
|
|
636
|
+
def _build_fastmcp():
|
|
637
|
+
import inspect
|
|
638
|
+
_kwargs = {}
|
|
639
|
+
try:
|
|
640
|
+
_params = inspect.signature(FastMCP.__init__).parameters
|
|
641
|
+
except (TypeError, ValueError): # pragma: no cover - defensive
|
|
642
|
+
_params = {}
|
|
643
|
+
_desc = "Loki Mode autonomous agent orchestration"
|
|
644
|
+
if "instructions" in _params:
|
|
645
|
+
_kwargs["instructions"] = _desc
|
|
646
|
+
elif "description" in _params:
|
|
647
|
+
_kwargs["description"] = _desc
|
|
648
|
+
if "version" in _params:
|
|
649
|
+
_kwargs["version"] = _version
|
|
650
|
+
return FastMCP("loki-mode", **_kwargs)
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
mcp = _build_fastmcp()
|
|
518
654
|
|
|
519
655
|
# ============================================================
|
|
520
656
|
# TOOLS - Functions Claude can call
|
|
@@ -2462,8 +2598,23 @@ def main():
|
|
|
2462
2598
|
help='Transport mechanism (default: stdio)')
|
|
2463
2599
|
parser.add_argument('--port', type=int, default=8421,
|
|
2464
2600
|
help='Port for HTTP transport (default: 8421)')
|
|
2601
|
+
parser.add_argument('--check-sdk', action='store_true',
|
|
2602
|
+
help=('Probe only: exit 0 if the MCP SDK loaded and the '
|
|
2603
|
+
'server object built, non-zero otherwise. Used by '
|
|
2604
|
+
'`loki mcp` to verify a venv before launching. '
|
|
2605
|
+
'Does not start a server.'))
|
|
2465
2606
|
args = parser.parse_args()
|
|
2466
2607
|
|
|
2608
|
+
# --check-sdk: if we reached here, the module-level loader already imported
|
|
2609
|
+
# FastMCP and built `mcp` (otherwise the module would have sys.exit(1)'d at
|
|
2610
|
+
# import). So reaching main() with a live `mcp` object means the SDK is
|
|
2611
|
+
# genuinely importable. Report success and exit without starting a server.
|
|
2612
|
+
if args.check_sdk:
|
|
2613
|
+
if mcp is not None:
|
|
2614
|
+
print("MCP SDK OK", file=sys.stderr)
|
|
2615
|
+
sys.exit(0)
|
|
2616
|
+
sys.exit(1)
|
|
2617
|
+
|
|
2467
2618
|
# Register cleanup to prevent file handle leaks on shutdown/restart
|
|
2468
2619
|
atexit.register(cleanup_mcp_singletons)
|
|
2469
2620
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.30.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -15,3 +15,6 @@ index.html in a browser runs the whole app.
|
|
|
15
15
|
|
|
16
16
|
Keep the whole thing minimal and readable. The goal is a quick end-to-end
|
|
17
17
|
build that finishes fast, not a production system.
|
|
18
|
+
|
|
19
|
+
A basic automated check or verification step confirms that add, list, toggle,
|
|
20
|
+
and delete behave as described before the build is called done.
|