adaptive-memory-multi-model-router 2.15.5 → 2.16.1

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.
Files changed (206) hide show
  1. package/.github/workflows/auto-submit-sitemap.yml +41 -0
  2. package/.github/workflows/mcp-pypi-publish.yml +34 -0
  3. package/.github/workflows/pypi-publish.yml +61 -17
  4. package/.github/workflows/tmlpd-publish.yml +23 -0
  5. package/README.md +181 -207
  6. package/RELEASE_v2.16.0.md +149 -0
  7. package/TECHNICAL_README.md +253 -0
  8. package/discoverability-diagnosis.md +280 -0
  9. package/dist/analytics/costAnalytics.d.ts.map +1 -1
  10. package/dist/benchmark/reproducible.d.ts.map +1 -1
  11. package/dist/cache/semanticCache.d.ts.map +1 -1
  12. package/dist/cli/setupWizard.d.ts +257 -50
  13. package/dist/cli/setupWizard.d.ts.map +1 -1
  14. package/dist/cli/setupWizard.js +419 -109
  15. package/dist/cli/setupWizard.js.map +1 -1
  16. package/dist/cli/tui.js +96 -67
  17. package/dist/cli.js +9 -0
  18. package/dist/cost/budgetEnforcer.d.ts.map +1 -1
  19. package/dist/cost/costTracker.d.ts.map +1 -1
  20. package/dist/ensemble/multiRoundDialog.d.ts.map +1 -1
  21. package/dist/ensemble/shapleyValue.d.ts.map +1 -1
  22. package/dist/ensemble.d.ts +1 -1
  23. package/dist/ensemble.js +141 -0
  24. package/dist/integrations/langchainAdapter.d.ts.map +1 -1
  25. package/dist/integrations/langchainAdapter.js +3 -3
  26. package/dist/integrations/langchainAdapter.js.map +1 -1
  27. package/dist/integrations/oauth.d.ts.map +1 -1
  28. package/dist/integrations/scienceAdapter.d.ts.map +1 -1
  29. package/dist/memory/autoFetch.d.ts.map +1 -1
  30. package/dist/memory/hybridMemory.d.ts.map +1 -1
  31. package/dist/memory/memoryTree.d.ts.map +1 -1
  32. package/dist/memory/obsidianVault.d.ts.map +1 -1
  33. package/dist/memory/reasoningBank.d.ts.map +1 -1
  34. package/dist/observability/metrics.d.ts.map +1 -1
  35. package/dist/observability/tracer.d.ts.map +1 -1
  36. package/dist/providers/providerConfig.d.ts.map +1 -1
  37. package/dist/providers/providerConfig.js +32 -17
  38. package/dist/providers/providerConfig.js.map +1 -1
  39. package/dist/routing/advancedRouter.d.ts.map +1 -1
  40. package/dist/routing/advancedRouter.js +106 -14
  41. package/dist/routing/advancedRouter.js.map +1 -1
  42. package/dist/routing/providerHealth.d.ts.map +1 -1
  43. package/dist/routing/providerRetry.d.ts.map +1 -1
  44. package/dist/routing/shadowSampler.js.map +1 -1
  45. package/dist/security/guardrails.d.ts.map +1 -1
  46. package/dist/server/handlers/chatHandler.d.ts.map +1 -1
  47. package/dist/server/handlers/completionsHandler.d.ts.map +1 -1
  48. package/dist/server/handlers/embeddingsHandler.d.ts.map +1 -1
  49. package/dist/server/handlers/healthHandler.d.ts.map +1 -1
  50. package/dist/server/handlers/metricsHandler.d.ts.map +1 -1
  51. package/dist/server/handlers/modelsHandler.d.ts.map +1 -1
  52. package/dist/server/metrics.d.ts.map +1 -1
  53. package/dist/server/proxyServer.d.ts.map +1 -1
  54. package/dist/server/router.d.ts.map +1 -1
  55. package/dist/server/state.d.ts.map +1 -1
  56. package/dist/skills/__tests__/skill_manager.test.js +5 -265
  57. package/dist/skills/__tests__/skill_manager.test.js.map +1 -1
  58. package/dist/utils/tokenUtils.d.ts.map +1 -1
  59. package/docs/ARTICLE_Biology_Inspired_Routing.md +208 -0
  60. package/docs/ARTICLE_Master.md +78 -0
  61. package/docs/ARTICLE_Master_CN.md +78 -0
  62. package/docs/ARTICLE_OpenRouter_Stripe.md +140 -0
  63. package/docs/DEVPTO_ARTICLE.md +84 -0
  64. package/docs/HUMAN_STYLE_GUIDE.md +75 -0
  65. package/docs/IMPRINT_PLAN.md +88 -0
  66. package/docs/OPENROUTER_ALTERNATIVE.md +184 -0
  67. package/docs/SOCIAL_CAMPAIGN.md +316 -0
  68. package/docs/anthropic.html +45 -0
  69. package/docs/best-llm-routers-2025.html +157 -0
  70. package/docs/cerebras.html +43 -0
  71. package/docs/cli-cheatsheet.md +286 -212
  72. package/docs/deepseek.html +44 -0
  73. package/docs/google.html +47 -0
  74. package/docs/groq.html +44 -0
  75. package/docs/mistral.html +43 -0
  76. package/docs/ollama.html +50 -0
  77. package/docs/openai.html +57 -0
  78. package/docs/sitemap.xml +69 -57
  79. package/docs-site/blog/best-llm-routers-2025.html +157 -0
  80. package/docs-site/index.html +59 -0
  81. package/docs-site/providers/anthropic.html +45 -0
  82. package/docs-site/providers/cerebras.html +43 -0
  83. package/docs-site/providers/deepseek.html +44 -0
  84. package/docs-site/providers/google.html +47 -0
  85. package/docs-site/providers/groq.html +44 -0
  86. package/docs-site/providers/index.html +41 -0
  87. package/docs-site/providers/mistral.html +43 -0
  88. package/docs-site/providers/ollama.html +50 -0
  89. package/docs-site/providers/openai.html +57 -0
  90. package/docs-site/sitemap.xml +69 -0
  91. package/package.json +36 -124
  92. package/python/README.md +33 -106
  93. package/python/mcp-server/a3m_mcp/__init__.py +20 -10
  94. package/python/mcp-server/a3m_mcp/server.py +172 -306
  95. package/python/pyproject.toml +6 -3
  96. package/scripts/submit-sitemap.sh +52 -0
  97. package/src/__types__/registry.d.ts +14 -0
  98. package/src/cli/setupWizard.ts +443 -112
  99. package/src/cli/tui.ts +159 -0
  100. package/src/ensemble.ts +154 -1
  101. package/src/integrations/langchainAdapter.ts +2 -2
  102. package/src/providers/providerConfig.ts +32 -17
  103. package/src/providers/registry.js +27 -0
  104. package/src/routing/advancedRouter.ts +99 -14
  105. package/src/routing/shadowSampler.ts +1 -1
  106. package/test-install/package.json +12 -0
  107. package/tests/tsconfig.json +0 -1
  108. package/tmlpd-pi-extension/README.md +105 -44
  109. package/tmlpd-pi-extension/docs/demo.svg +33 -0
  110. package/tmlpd-pi-extension/package.json +35 -106
  111. package/tmlpd-pi-extension/src/tokenOptimization/contextStratifier.ts +163 -0
  112. package/tmlpd-pi-extension/src/tokenOptimization/fetchOnceLocal.ts +136 -0
  113. package/tmlpd-pi-extension/src/tokenOptimization/index.ts +197 -0
  114. package/tmlpd-pi-extension/src/tokenOptimization/interAgentCompression.ts +157 -0
  115. package/tmlpd-pi-extension/src/tokenOptimization/schemaContract.ts +101 -0
  116. package/tmlpd-pi-extension/src/tokenOptimization/semanticCache.ts +248 -0
  117. package/tmlpd-pi-extension/src/tokenOptimization/tokenAwareFallback.ts +192 -0
  118. package/tmlpd-pi-extension/test/verify.js +21 -0
  119. package/tsconfig.build.json +2 -0
  120. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +0 -12
  121. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +0 -1
  122. package/packages/a3m-vercel-ai/dist/a3m-language-model.js +0 -289
  123. package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +0 -1
  124. package/packages/a3m-vercel-ai/dist/index.d.ts +0 -82
  125. package/packages/a3m-vercel-ai/dist/index.d.ts.map +0 -1
  126. package/packages/a3m-vercel-ai/dist/index.js +0 -79
  127. package/packages/a3m-vercel-ai/dist/index.js.map +0 -1
  128. package/packages/a3m-vercel-ai/dist/types.d.ts +0 -97
  129. package/packages/a3m-vercel-ai/dist/types.d.ts.map +0 -1
  130. package/packages/a3m-vercel-ai/dist/types.js +0 -5
  131. package/packages/a3m-vercel-ai/dist/types.js.map +0 -1
  132. package/python/a3m_router.egg-info/PKG-INFO +0 -172
  133. package/python/a3m_router.egg-info/SOURCES.txt +0 -17
  134. package/python/a3m_router.egg-info/dependency_links.txt +0 -1
  135. package/python/a3m_router.egg-info/requires.txt +0 -24
  136. package/python/a3m_router.egg-info/top_level.txt +0 -1
  137. package/python/dist/a3m_router-2.2.1-py3-none-any.whl +0 -0
  138. package/python/dist/a3m_router-2.2.1.tar.gz +0 -0
  139. package/python/dist/a3m_router-2.2.2-py3-none-any.whl +0 -0
  140. package/python/dist/a3m_router-2.2.2.tar.gz +0 -0
  141. package/src/skills/__tests__/skill_manager.test.ts +0 -328
  142. package/tmlpd-pi-extension/dist/cache/prefixCache.d.ts +0 -114
  143. package/tmlpd-pi-extension/dist/cache/prefixCache.d.ts.map +0 -1
  144. package/tmlpd-pi-extension/dist/cache/prefixCache.js +0 -285
  145. package/tmlpd-pi-extension/dist/cache/prefixCache.js.map +0 -1
  146. package/tmlpd-pi-extension/dist/cache/responseCache.d.ts +0 -58
  147. package/tmlpd-pi-extension/dist/cache/responseCache.d.ts.map +0 -1
  148. package/tmlpd-pi-extension/dist/cache/responseCache.js +0 -153
  149. package/tmlpd-pi-extension/dist/cache/responseCache.js.map +0 -1
  150. package/tmlpd-pi-extension/dist/cli.js +0 -59
  151. package/tmlpd-pi-extension/dist/cost/costTracker.d.ts +0 -95
  152. package/tmlpd-pi-extension/dist/cost/costTracker.d.ts.map +0 -1
  153. package/tmlpd-pi-extension/dist/cost/costTracker.js +0 -240
  154. package/tmlpd-pi-extension/dist/cost/costTracker.js.map +0 -1
  155. package/tmlpd-pi-extension/dist/index.d.ts +0 -723
  156. package/tmlpd-pi-extension/dist/index.d.ts.map +0 -1
  157. package/tmlpd-pi-extension/dist/index.js +0 -239
  158. package/tmlpd-pi-extension/dist/index.js.map +0 -1
  159. package/tmlpd-pi-extension/dist/memory/episodicMemory.d.ts +0 -82
  160. package/tmlpd-pi-extension/dist/memory/episodicMemory.d.ts.map +0 -1
  161. package/tmlpd-pi-extension/dist/memory/episodicMemory.js +0 -145
  162. package/tmlpd-pi-extension/dist/memory/episodicMemory.js.map +0 -1
  163. package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.d.ts +0 -102
  164. package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.d.ts.map +0 -1
  165. package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.js +0 -207
  166. package/tmlpd-pi-extension/dist/orchestration/haloOrchestrator.js.map +0 -1
  167. package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.d.ts +0 -85
  168. package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.d.ts.map +0 -1
  169. package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.js +0 -210
  170. package/tmlpd-pi-extension/dist/orchestration/mctsWorkflow.js.map +0 -1
  171. package/tmlpd-pi-extension/dist/providers/localProvider.d.ts +0 -102
  172. package/tmlpd-pi-extension/dist/providers/localProvider.d.ts.map +0 -1
  173. package/tmlpd-pi-extension/dist/providers/localProvider.js +0 -338
  174. package/tmlpd-pi-extension/dist/providers/localProvider.js.map +0 -1
  175. package/tmlpd-pi-extension/dist/providers/registry.d.ts +0 -55
  176. package/tmlpd-pi-extension/dist/providers/registry.d.ts.map +0 -1
  177. package/tmlpd-pi-extension/dist/providers/registry.js +0 -138
  178. package/tmlpd-pi-extension/dist/providers/registry.js.map +0 -1
  179. package/tmlpd-pi-extension/dist/routing/advancedRouter.d.ts +0 -68
  180. package/tmlpd-pi-extension/dist/routing/advancedRouter.d.ts.map +0 -1
  181. package/tmlpd-pi-extension/dist/routing/advancedRouter.js +0 -332
  182. package/tmlpd-pi-extension/dist/routing/advancedRouter.js.map +0 -1
  183. package/tmlpd-pi-extension/dist/tools/tmlpdTools.d.ts +0 -101
  184. package/tmlpd-pi-extension/dist/tools/tmlpdTools.d.ts.map +0 -1
  185. package/tmlpd-pi-extension/dist/tools/tmlpdTools.js +0 -368
  186. package/tmlpd-pi-extension/dist/tools/tmlpdTools.js.map +0 -1
  187. package/tmlpd-pi-extension/dist/utils/batchProcessor.d.ts +0 -96
  188. package/tmlpd-pi-extension/dist/utils/batchProcessor.d.ts.map +0 -1
  189. package/tmlpd-pi-extension/dist/utils/batchProcessor.js +0 -170
  190. package/tmlpd-pi-extension/dist/utils/batchProcessor.js.map +0 -1
  191. package/tmlpd-pi-extension/dist/utils/compression.d.ts +0 -61
  192. package/tmlpd-pi-extension/dist/utils/compression.d.ts.map +0 -1
  193. package/tmlpd-pi-extension/dist/utils/compression.js +0 -281
  194. package/tmlpd-pi-extension/dist/utils/compression.js.map +0 -1
  195. package/tmlpd-pi-extension/dist/utils/reliability.d.ts +0 -74
  196. package/tmlpd-pi-extension/dist/utils/reliability.d.ts.map +0 -1
  197. package/tmlpd-pi-extension/dist/utils/reliability.js +0 -177
  198. package/tmlpd-pi-extension/dist/utils/reliability.js.map +0 -1
  199. package/tmlpd-pi-extension/dist/utils/speculativeDecoding.d.ts +0 -117
  200. package/tmlpd-pi-extension/dist/utils/speculativeDecoding.d.ts.map +0 -1
  201. package/tmlpd-pi-extension/dist/utils/speculativeDecoding.js +0 -246
  202. package/tmlpd-pi-extension/dist/utils/speculativeDecoding.js.map +0 -1
  203. package/tmlpd-pi-extension/dist/utils/tokenUtils.d.ts +0 -50
  204. package/tmlpd-pi-extension/dist/utils/tokenUtils.d.ts.map +0 -1
  205. package/tmlpd-pi-extension/dist/utils/tokenUtils.js +0 -124
  206. package/tmlpd-pi-extension/dist/utils/tokenUtils.js.map +0 -1
@@ -1,339 +1,205 @@
1
- """A3M Router MCP Server — 4 tools for parallel multi-LLM routing.
2
-
3
- Tools:
4
- a3m_route - Route a query to the optimal LLM provider (no execution)
5
- a3m_ensemble - Execute query across multiple providers in parallel
6
- a3m_classify - Classify query type and get provider recommendations
7
- a3m_providers - List all configured providers with cost and availability
8
-
9
- Run:
10
- python -m a3m_mcp
11
- # Or pipe JSON-RPC directly:
12
- echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python -m a3m_mcp
13
1
  """
2
+ A3M Router MCP Server - MCP Protocol Server for A3M Router
3
+
4
+ MCP 2.0.0 compatible server that provides A3M Router functionality
5
+ as MCP tools for AI agents.
6
+ """
7
+
8
+ import os
14
9
  import asyncio
15
10
  import json
16
- import os
17
- import sys
18
- from typing import Any, Dict, List, Optional
11
+ from typing import Optional, List, Any
19
12
 
20
- # MCP imports — use correct lowlevel API for installed version
21
13
  try:
22
14
  from mcp.server.lowlevel import Server
15
+ from mcp import Tool, ListToolsResult
16
+ from mcp.types import CallToolResult, TextContent
23
17
  from mcp.server.stdio import stdio_server
24
- from mcp.types import Tool, CallToolResult, TextContent
25
18
  HAS_MCP = True
26
- except ImportError as e:
19
+ except ImportError:
27
20
  HAS_MCP = False
28
- print(f"WARNING: mcp package not installed. Run: pip install mcp. Error: {e}", file=sys.stderr)
29
-
30
- # ─── A3M Router client ──────────────────────────────────────────────────────
31
-
32
- A3M_BASE_URL = os.getenv("A3M_BASE_URL", "http://localhost:8787")
33
- A3M_API_KEY = os.getenv("A3M_API_KEY", "not-needed")
34
- A3M_TIMEOUT = float(os.getenv("A3M_TIMEOUT", "30.0"))
21
+ print("WARNING: mcp package not installed. Run: pip install mcp", flush=True)
35
22
 
36
-
37
- class A3MRouterClient:
38
- """Thin async client for the A3M Router HTTP API."""
39
-
40
- def __init__(self, base_url: str = A3M_BASE_URL, api_key: str = A3M_API_KEY):
41
- self.base_url = base_url.rstrip("/")
42
- self.api_key = api_key
43
- self._client: Optional[Any] = None
44
-
45
- async def _get_client(self) -> Any:
46
- if self._client is None:
47
- import httpx
48
- self._client = httpx.AsyncClient(
49
- base_url=self.base_url,
50
- timeout=A3M_TIMEOUT,
51
- headers={"Authorization": f"Bearer {self.api_key}"},
52
- )
53
- return self._client
54
-
55
- async def close(self):
56
- if self._client:
57
- await self._client.aclose()
58
- self._client = None
59
-
60
- async def route(self, query: str) -> Dict[str, Any]:
61
- """Route a query — returns routing decision without LLM execution."""
62
- client = await self._get_client()
63
- try:
64
- response = await client.get(
65
- "/route",
66
- params={"query": query},
67
- headers={"Authorization": f"Bearer {self.api_key}"},
68
- )
69
- if response.status_code == 200:
70
- return response.json()
71
- except Exception:
72
- pass
73
- # Fallback
74
- return {
75
- "model": "groq/llama-3.3-70b-versatile",
76
- "tier": "cheap",
77
- "cost": 0.0,
78
- "reasoning": "A3M adaptive routing",
79
- "complexity": 0.3,
80
- }
81
-
82
- async def chat(
83
- self,
84
- query: str,
85
- model: str = "auto",
86
- system: Optional[str] = None,
87
- max_tokens: int = 1024,
88
- temperature: float = 0.7,
89
- ) -> Dict[str, Any]:
90
- """Send a chat message via A3M Router."""
91
- client = await self._get_client()
92
- messages = []
93
- if system:
94
- messages.append({"role": "system", "content": system})
95
- messages.append({"role": "user", "content": query})
96
-
97
- try:
98
- response = await client.post(
99
- "/v1/chat/completions",
100
- json={
101
- "model": model,
102
- "messages": messages,
103
- "max_tokens": max_tokens,
104
- "temperature": temperature,
23
+ # A3M Router client
24
+ try:
25
+ import a3m
26
+ A3MRouter = a3m.A3MRouter
27
+ except ImportError:
28
+ A3MRouter = None
29
+
30
+
31
+ SERVER_NAME = "a3m-router"
32
+ SERVER_VERSION = "1.0.0"
33
+
34
+ _router: Optional["A3MRouter"] = None
35
+
36
+
37
+ def get_router() -> "A3MRouter":
38
+ """Get or create A3MRouter instance."""
39
+ global _router
40
+ if _router is None:
41
+ base_url = os.environ.get("A3M_BASE_URL", "http://localhost:8787")
42
+ api_key = os.environ.get("A3M_API_KEY", "")
43
+ _router = A3MRouter(base_url=base_url, api_key=api_key)
44
+ return _router
45
+
46
+
47
+ # Tool definitions
48
+ TOOLS = [
49
+ Tool(
50
+ name="a3m_route",
51
+ title="Route Query",
52
+ description="Route a query to the optimal LLM provider using A3M's adaptive routing. Returns model selection, tier, estimated cost, and reasoning.",
53
+ inputSchema={
54
+ "type": "object",
55
+ "properties": {
56
+ "query": {
57
+ "type": "string",
58
+ "description": "The user query to route (e.g. 'Write Python code to sort a list')",
59
+ },
60
+ "provider": {
61
+ "type": "string",
62
+ "description": "Optional: Force a specific provider (e.g. 'groq', 'google')",
63
+ },
64
+ },
65
+ "required": ["query"],
66
+ },
67
+ ),
68
+ Tool(
69
+ name="a3m_chat",
70
+ title="Chat with A3M",
71
+ description="Send a chat message through A3M Router's adaptive routing.",
72
+ inputSchema={
73
+ "type": "object",
74
+ "properties": {
75
+ "query": {
76
+ "type": "string",
77
+ "description": "The user query",
105
78
  },
106
- headers={"Authorization": f"Bearer {self.api_key}"},
107
- )
108
- response.raise_for_status()
109
- return response.json()
110
- except Exception as e:
111
- return {"error": str(e), "choices": [{"message": {"content": f"Error: {e}"}}]}
112
-
113
- async def ensemble(
114
- self,
115
- query: str,
116
- providers: Optional[List[str]] = None,
117
- system: Optional[str] = None,
118
- ) -> Dict[str, Any]:
119
- """Execute query across multiple providers in parallel."""
120
- providers = providers or ["groq", "google", "cerebras"]
121
- tasks = [
122
- self.chat(query, model=p, system=system)
123
- for p in providers
124
- ]
125
- results = await asyncio.gather(*tasks, return_exceptions=True)
126
-
127
- responses = []
128
- for i, r in enumerate(results):
129
- if isinstance(r, Exception):
130
- responses.append({"provider": providers[i], "error": str(r)})
131
- else:
132
- content = r.get("choices", [{}])[0].get("message", {}).get("content", "")
133
- responses.append({"provider": providers[i], "content": content})
134
-
135
- return {
136
- "query": query,
137
- "parallel_responses": responses,
138
- "best_answer": responses[0].get("content", "") if responses else "",
139
- "stats": {
140
- "total_providers": len(providers),
141
- "successful": sum(1 for r in responses if "error" not in r),
142
- "failed": sum(1 for r in responses if "error" in r),
143
79
  },
144
- }
145
-
146
- async def classify(self, query: str) -> Dict[str, Any]:
147
- """Classify a query by type and get provider recommendations."""
148
- route = await self.route(query)
149
- complexity = route.get("complexity", 0.5)
150
- model = route.get("model", "groq/llama-3.3-70b-versatile")
151
-
152
- query_lower = query.lower()
153
- if any(k in query_lower for k in ["write code", "function", "class", "def ", "import "]):
154
- classification = "code"
155
- recommended = ["groq/llama-3.3-70b-versatile", "cerebras/cerebras-coder"]
156
- elif any(k in query_lower for k in ["explain", "what is", "how does", "why is"]):
157
- classification = "fast"
158
- recommended = ["groq/llama-3.1-8b-instant", "google/gemini-2.0-flash"]
159
- elif complexity >= 0.65:
160
- classification = "deep"
161
- recommended = ["openai/gpt-4o", "anthropic/claude-3.5-sonnet"]
162
- else:
163
- classification = "creative"
164
- recommended = ["groq/llama-3.3-70b-versatile", "google/gemini-2.0-flash"]
165
-
166
- return {
167
- "query": query,
168
- "classification": classification,
169
- "complexity": complexity,
170
- "recommended_model": model,
171
- "recommended_providers": recommended,
172
- }
173
-
174
- async def providers(self) -> Dict[str, Any]:
175
- """List all configured providers with cost and status."""
176
- client = await self._get_client()
80
+ "required": ["query"],
81
+ },
82
+ ),
83
+ Tool(
84
+ name="a3m_cost_report",
85
+ title="Cost Report",
86
+ description="Get cost analytics across all providers.",
87
+ inputSchema={
88
+ "type": "object",
89
+ "properties": {},
90
+ },
91
+ ),
92
+ Tool(
93
+ name="a3m_models",
94
+ title="List Models",
95
+ description="List all available models with cost/latency info.",
96
+ inputSchema={
97
+ "type": "object",
98
+ "properties": {},
99
+ },
100
+ ),
101
+ ]
102
+
103
+
104
+ class A3MServer:
105
+ """A3M Router MCP Server with tool handlers."""
106
+
107
+ def __init__(self):
108
+ pass
109
+
110
+ async def handle_list_tools(self, request_context=None) -> ListToolsResult:
111
+ """Handle tools/list request."""
112
+ return ListToolsResult(
113
+ tools=[
114
+ {
115
+ "name": t.name,
116
+ "description": t.description,
117
+ "inputSchema": t.inputSchema,
118
+ }
119
+ for t in TOOLS
120
+ ]
121
+ )
122
+
123
+ async def handle_call_tool(self, request_context, name: str, arguments: dict) -> CallToolResult:
124
+ """Handle tools/call request."""
177
125
  try:
178
- response = await client.get(
179
- "/providers",
180
- headers={"Authorization": f"Bearer {self.api_key}"},
181
- )
182
- if response.status_code == 200:
183
- return response.json()
184
- except Exception:
185
- pass
186
-
187
- default_providers = [
188
- {"name": "groq", "models": ["llama-3.3-70b-versatile", "llama-3.1-8b-instant"], "cost_per_1k_input": 0.0, "status": "available"},
189
- {"name": "google", "models": ["gemini-2.0-flash", "gemini-1.5-flash"], "cost_per_1k_input": 0.0, "status": "available"},
190
- {"name": "cerebras", "models": ["cerebras-coder", "cerebras-llama-3.3-70b"], "cost_per_1k_input": 0.0, "status": "available"},
191
- {"name": "openai", "models": ["gpt-4o", "gpt-4o-mini"], "cost_per_1k_input": 0.015, "status": "available"},
192
- {"name": "anthropic", "models": ["claude-3.5-sonnet", "claude-3-opus"], "cost_per_1k_input": 0.015, "status": "available"},
193
- {"name": "mistral", "models": ["mistral-large", "mistral-small"], "cost_per_1k_input": 0.008, "status": "available"},
194
- {"name": "deepseek", "models": ["deepseek-chat", "deepseek-coder"], "cost_per_1k_input": 0.001, "status": "available"},
195
- ]
196
- return {"providers": default_providers, "source": "fallback"}
197
-
126
+ if name == "a3m_route":
127
+ router = get_router()
128
+ query = arguments.get("query")
129
+ provider = arguments.get("provider")
198
130
 
199
- # ─── MCP Server ───────────────────────────────────────────────────────────────
131
+ result = router.route(query, provider=provider)
132
+ return CallToolResult(
133
+ content=[TextContent(type="text", text=json.dumps(result, indent=2))]
134
+ )
200
135
 
201
- if HAS_MCP:
202
- SERVER_NAME = "a3m-router"
203
- SERVER_VERSION = "1.0.0"
204
-
205
- _router: Optional[A3MRouterClient] = None
206
-
207
- def get_router() -> A3MRouterClient:
208
- global _router
209
- if _router is None:
210
- _router = A3MRouterClient()
211
- return _router
212
-
213
- server = Server(SERVER_NAME)
214
-
215
- @server.list_tools()
216
- async def list_tools() -> List[Tool]:
217
- """List all available MCP tools."""
218
- return [
219
- Tool(
220
- name="a3m_route",
221
- description="Route a query to the optimal LLM provider using A3M's adaptive routing. Returns model selection, tier, estimated cost, and reasoning — no LLM execution.",
222
- inputSchema={
223
- "type": "object",
224
- "properties": {
225
- "query": {
226
- "type": "string",
227
- "description": "The user query to route (e.g. 'Write Python code to sort a list')",
228
- }
229
- },
230
- "required": ["query"],
231
- },
232
- ),
233
- Tool(
234
- name="a3m_ensemble",
235
- description="Execute a query across multiple LLM providers in parallel and merge results. Returns all responses with stats on success/failure per provider.",
236
- inputSchema={
237
- "type": "object",
238
- "properties": {
239
- "query": {
240
- "type": "string",
241
- "description": "The user query to execute across providers",
242
- },
243
- "providers": {
244
- "type": "array",
245
- "items": {"type": "string"},
246
- "description": "Optional list of provider names. Defaults to ['groq', 'google', 'cerebras']",
247
- },
248
- "system": {
249
- "type": "string",
250
- "description": "Optional system prompt to prepend",
251
- },
252
- },
253
- "required": ["query"],
254
- },
255
- ),
256
- Tool(
257
- name="a3m_classify",
258
- description="Classify a query by type (fast/creative/deep/code) and get provider recommendations based on query complexity.",
259
- inputSchema={
260
- "type": "object",
261
- "properties": {
262
- "query": {
263
- "type": "string",
264
- "description": "The query to classify",
265
- }
266
- },
267
- "required": ["query"],
268
- },
269
- ),
270
- Tool(
271
- name="a3m_providers",
272
- description="List all configured LLM providers with their models, cost per 1K tokens, and availability status.",
273
- inputSchema={
274
- "type": "object",
275
- "properties": {},
276
- },
277
- ),
278
- ]
136
+ elif name == "a3m_chat":
137
+ router = get_router()
138
+ query = arguments.get("query")
279
139
 
280
- @server.call_tool()
281
- async def call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResult:
282
- """Execute an MCP tool call."""
283
- router = get_router()
284
- try:
285
- if name == "a3m_route":
286
- result = await router.route(arguments["query"])
287
- text = json.dumps(result, indent=2)
288
- return CallToolResult(content=[TextContent(type="text", text=text)])
289
-
290
- elif name == "a3m_ensemble":
291
- result = await router.ensemble(
292
- query=arguments["query"],
293
- providers=arguments.get("providers"),
294
- system=arguments.get("system"),
140
+ result = router.chat(query)
141
+ return CallToolResult(
142
+ content=[TextContent(type="text", text=str(result))]
295
143
  )
296
- text = json.dumps(result, indent=2)
297
- return CallToolResult(content=[TextContent(type="text", text=text)])
298
144
 
299
- elif name == "a3m_classify":
300
- result = await router.classify(arguments["query"])
301
- text = json.dumps(result, indent=2)
302
- return CallToolResult(content=[TextContent(type="text", text=text)])
145
+ elif name == "a3m_cost_report":
146
+ router = get_router()
147
+ report = router.cost_report()
148
+ return CallToolResult(
149
+ content=[TextContent(type="text", text=json.dumps(report, indent=2))]
150
+ )
303
151
 
304
- elif name == "a3m_providers":
305
- result = await router.providers()
306
- text = json.dumps(result, indent=2)
307
- return CallToolResult(content=[TextContent(type="text", text=text)])
152
+ elif name == "a3m_models":
153
+ router = get_router()
154
+ models = router.models()
155
+ return CallToolResult(
156
+ content=[TextContent(type="text", text=json.dumps(models, indent=2))]
157
+ )
308
158
 
309
159
  else:
310
160
  return CallToolResult(
311
161
  content=[TextContent(type="text", text=f"Unknown tool: {name}")],
312
162
  isError=True,
313
163
  )
164
+
314
165
  except Exception as e:
315
166
  return CallToolResult(
316
167
  content=[TextContent(type="text", text=f"Error: {str(e)}")],
317
168
  isError=True,
318
169
  )
319
- finally:
320
- await router.close()
321
-
322
-
323
- async def main():
324
- """Run the MCP server over stdio."""
325
- print("A3M Router MCP Server starting...", file=sys.stderr)
326
- print(f"Connecting to A3M Router at {A3M_BASE_URL}", file=sys.stderr)
327
- async with stdio_server() as (read_stream, write_stream):
328
- await server.run(
329
- read_stream,
330
- write_stream,
331
- server.create_initialization_options(),
332
- )
333
170
 
334
171
 
335
- if __name__ == "__main__":
172
+ if HAS_MCP:
173
+ # Create server with handlers
174
+ a3m_server = A3MServer()
175
+
176
+ server = Server(
177
+ SERVER_NAME,
178
+ version=SERVER_VERSION,
179
+ on_list_tools=a3m_server.handle_list_tools,
180
+ on_call_tool=a3m_server.handle_call_tool,
181
+ )
182
+
183
+
184
+ async def main():
185
+ """Main entry point for MCP server."""
336
186
  if not HAS_MCP:
337
- print("ERROR: mcp package required. Install with: pip install mcp", file=sys.stderr)
338
- sys.exit(1)
339
- asyncio.run(main())
187
+ print("ERROR: mcp package not installed", flush=True)
188
+ return
189
+
190
+ print(f"Starting {SERVER_NAME} MCP Server v{SERVER_VERSION}", flush=True)
191
+
192
+ async with stdio_server() as (read_stream, write_stream):
193
+ await server.run(
194
+ read_stream,
195
+ write_stream,
196
+ server.create_initialization_options(),
197
+ )
198
+
199
+
200
+ if __name__ == "__main__":
201
+ if HAS_MCP:
202
+ asyncio.run(main())
203
+ else:
204
+ print("ERROR: mcp package required", flush=True)
205
+ print("Run: pip install mcp", flush=True)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "a3m-router"
7
- version = "2.2.2"
7
+ version = "2.16.1"
8
8
  description = "Auto-selects cheapest capable LLM from 47+ providers — 70-95% cost savings"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -61,10 +61,13 @@ adapters = [
61
61
  dev = ["pytest>=7.4.0", "pytest-asyncio>=0.21.0", "black>=23.0.0"]
62
62
 
63
63
  [project.urls]
64
- Homepage = "https://github.com/Das-rebel/a3m-router"
65
- Documentation = "https://das-rebel.github.io/a3m-router"
64
+ Homepage = "https://github.com/Das-rebel/a3m-router#a3m-router"
65
+ Documentation = "https://github.com/Das-rebel/a3m-router#readme"
66
66
  Repository = "https://github.com/Das-rebel/a3m-router"
67
67
  Changelog = "https://github.com/Das-rebel/a3m-router/releases"
68
+ "NPM Package" = "https://www.npmjs.com/package/adaptive-memory-multi-model-router"
69
+ "PyPI Package" = "https://pypi.org/project/a3m-router/"
70
+ "GitHub Stars" = "https://github.com/Das-rebel/a3m-router/stargazers"
68
71
 
69
72
  [tool.setuptools.packages.find]
70
73
  where = ["."]
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ #
3
+ # A3M Router — Sitemap Submission Script
4
+ # Submits sitemap to Google, Bing, and IndexNow for faster indexing
5
+ #
6
+ set -e
7
+
8
+ SITEMAP_URL="${SITEMAP_URL:-https://a3m-router.com/sitemap.xml}"
9
+ INDEXNOW_KEY="${INDEXNOW_KEY:-}"
10
+
11
+ echo "=== A3M Router Sitemap Submission ==="
12
+ echo "Sitemap: $SITEMAP_URL"
13
+ echo ""
14
+
15
+ # ─── Google ───────────────────────────────────────────
16
+ echo "[1/3] Submitting to Google..."
17
+ GOOGLE_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
18
+ "https://www.google.com/ping?sitemap=$SITEMAP_URL")
19
+ if [ "$GOOGLE_STATUS" = "200" ]; then
20
+ echo " ✅ Google: OK (HTTP $GOOGLE_STATUS)"
21
+ else
22
+ echo " ⚠️ Google: HTTP $GOOGLE_STATUS"
23
+ fi
24
+
25
+ # ─── Bing ─────────────────────────────────────────────
26
+ echo "[2/3] Submitting to Bing..."
27
+ BING_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
28
+ "https://www.bing.com/ping?sitemap=$SITEMAP_URL")
29
+ if [ "$BING_STATUS" = "200" ]; then
30
+ echo " ✅ Bing: OK (HTTP $BING_STATUS)"
31
+ else
32
+ echo " ⚠️ Bing: HTTP $BING_STATUS"
33
+ fi
34
+
35
+ # ─── IndexNow ─────────────────────────────────────────
36
+ echo "[3/3] Submitting to IndexNow..."
37
+ if [ -z "$INDEXNOW_KEY" ]; then
38
+ echo " ⚠️ Skipped: INDEXNOW_KEY env var not set"
39
+ echo " To enable IndexNow: export INDEXNOW_KEY=your_key_from_indexnow.org"
40
+ else
41
+ # Get the sitemap URLs and submit each to IndexNow
42
+ curl -s "$SITEMAP_URL" | grep -oP '(?<=<loc>)[^<]+' | while read -r url; do
43
+ RESPONSE=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
44
+ "https://indexnow.org/ping" \
45
+ -H "Content-Type: text/plain" \
46
+ -d "url=$url&key=$INDEXNOW_KEY")
47
+ echo " IndexNow: $url → HTTP $RESPONSE"
48
+ done
49
+ fi
50
+
51
+ echo ""
52
+ echo "=== Done ==="
@@ -0,0 +1,14 @@
1
+ declare module '@my-org/a3m-router/src/providers/registry' {
2
+ const PROVIDERS: Map<string, any>;
3
+ const registry: Map<string, any>;
4
+ export function registerProvider(name: string, config: any): void;
5
+ export function getProvider(name: string): any;
6
+ export function listProviders(): Array<[string, any]>;
7
+ export interface ProviderRegistry {
8
+ registerProvider: (name: string, config: any) => void;
9
+ getProvider: (name: string) => any;
10
+ listProviders: () => Array<[string, any]>;
11
+ }
12
+ const registryInstance: ProviderRegistry;
13
+ export default registryInstance;
14
+ }