synapse-framework 0.2.4__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.
Files changed (54) hide show
  1. synapse/__init__.py +207 -0
  2. synapse/app.py +942 -0
  3. synapse/cli.py +88 -0
  4. synapse/config.py +103 -0
  5. synapse/connectors/__init__.py +29 -0
  6. synapse/connectors/base.py +222 -0
  7. synapse/connectors/factory.py +202 -0
  8. synapse/connectors/gmail.py +292 -0
  9. synapse/connectors/mcp_wrapper.py +153 -0
  10. synapse/connectors/notion.py +161 -0
  11. synapse/connectors/photos.py +141 -0
  12. synapse/database/__init__.py +19 -0
  13. synapse/database/base.py +63 -0
  14. synapse/database/chroma.py +175 -0
  15. synapse/database/memory.py +132 -0
  16. synapse/database/pgvector.py +99 -0
  17. synapse/database/qdrant.py +138 -0
  18. synapse/embeddings/__init__.py +21 -0
  19. synapse/embeddings/base.py +262 -0
  20. synapse/export/__init__.py +14 -0
  21. synapse/export/mcp_exporter.py +202 -0
  22. synapse/graph/__init__.py +21 -0
  23. synapse/graph/base.py +130 -0
  24. synapse/graph/memory.py +20 -0
  25. synapse/graph/neo4j_store.py +176 -0
  26. synapse/graph/networkx_store.py +221 -0
  27. synapse/inspector/__init__.py +5 -0
  28. synapse/inspector/template.py +530 -0
  29. synapse/models/__init__.py +73 -0
  30. synapse/models/base.py +95 -0
  31. synapse/models/gemini.py +328 -0
  32. synapse/models/ollama.py +322 -0
  33. synapse/models/openai.py +289 -0
  34. synapse/oauth/__init__.py +7 -0
  35. synapse/oauth/google.py +194 -0
  36. synapse/oauth/notion.py +35 -0
  37. synapse/rag/__init__.py +8 -0
  38. synapse/rag/chunker.py +219 -0
  39. synapse/rag/hybrid_rag.py +122 -0
  40. synapse/resource/__init__.py +5 -0
  41. synapse/resource/resource.py +304 -0
  42. synapse/resources/__init__.py +5 -0
  43. synapse/skills/__init__.py +5 -0
  44. synapse/skills/skill.py +321 -0
  45. synapse/storage/__init__.py +24 -0
  46. synapse/storage/graph_exporter.py +624 -0
  47. synapse/storage/graph_store.py +162 -0
  48. synapse/storage/vector_store.py +103 -0
  49. synapse_framework-0.2.4.dist-info/METADATA +415 -0
  50. synapse_framework-0.2.4.dist-info/RECORD +54 -0
  51. synapse_framework-0.2.4.dist-info/WHEEL +5 -0
  52. synapse_framework-0.2.4.dist-info/entry_points.txt +2 -0
  53. synapse_framework-0.2.4.dist-info/licenses/LICENSE +21 -0
  54. synapse_framework-0.2.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,138 @@
1
+ """Qdrant vector database provider for Synapse."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Union
6
+
7
+ from langchain_core.documents import Document
8
+
9
+ from synapse.database.base import BaseVectorDB
10
+ from synapse.embeddings import resolve_embedding
11
+
12
+
13
+ class QdrantDB(BaseVectorDB):
14
+ """Qdrant vector database adapter supporting local memory, disk, or remote cloud cluster."""
15
+
16
+ def __init__(
17
+ self,
18
+ location: str | None = ":memory:",
19
+ url: str | None = None,
20
+ api_key: str | None = None,
21
+ path: str | None = None,
22
+ collection_name: str = "synapse_collection",
23
+ embedding: Union[str, Any] | None = None,
24
+ embedding_model: str | None = None,
25
+ embedding_function: Any | None = None,
26
+ **kwargs: Any,
27
+ ) -> None:
28
+ self.collection_name = collection_name
29
+ self.embedding_function = resolve_embedding(
30
+ embedding=embedding or embedding_function or embedding_model,
31
+ **kwargs,
32
+ )
33
+
34
+ try:
35
+ from langchain_community.vectorstores import Qdrant
36
+ from qdrant_client import QdrantClient
37
+
38
+ if url is not None:
39
+ self.client = QdrantClient(url=url, api_key=api_key)
40
+ elif path is not None:
41
+ self.client = QdrantClient(path=path)
42
+ else:
43
+ self.client = QdrantClient(location=location or ":memory:")
44
+
45
+ self.store = Qdrant(
46
+ client=self.client,
47
+ collection_name=collection_name,
48
+ embeddings=self.embedding_function,
49
+ )
50
+ except ImportError:
51
+ # Fallback wrapper if qdrant-client is not installed
52
+ from synapse.database.memory import InMemoryDB
53
+ self._fallback = InMemoryDB(
54
+ embedding_model=embedding_model,
55
+ embedding_function=self.embedding_function,
56
+ )
57
+ self.store = None
58
+
59
+ def add_documents(
60
+ self,
61
+ documents: list[Document],
62
+ embeddings: list[list[float]] | None = None,
63
+ ) -> list[str]:
64
+ if self.store:
65
+ return self.store.add_documents(documents)
66
+ return self._fallback.add_documents(documents, embeddings)
67
+
68
+ def similarity_search(
69
+ self,
70
+ query: str,
71
+ k: int = 4,
72
+ filter_dict: dict[str, Any] | None = None,
73
+ query_embedding: list[float] | None = None,
74
+ ) -> list[Document]:
75
+ if self.store:
76
+ return self.store.similarity_search(query=query, k=k)
77
+ return self._fallback.similarity_search(query=query, k=k, filter_dict=filter_dict)
78
+
79
+ def count(self) -> int:
80
+ if self.store:
81
+ try:
82
+ res = self.client.get_collection(self.collection_name)
83
+ return getattr(res, "points_count", 0) or 0
84
+ except Exception:
85
+ return 0
86
+ return self._fallback.count()
87
+
88
+ def clear(self) -> None:
89
+ if self.store:
90
+ try:
91
+ self.client.delete_collection(self.collection_name)
92
+ except Exception:
93
+ pass # nosec B110
94
+ else:
95
+ self._fallback.clear()
96
+
97
+
98
+ def qdrant(
99
+ location: str | None = ":memory:",
100
+ url: str | None = None,
101
+ api_key: str | None = None,
102
+ path: str | None = None,
103
+ collection_name: str = "synapse_collection",
104
+ embedding: Union[str, Any] | None = None,
105
+ embedding_model: str | None = None,
106
+ embedding_function: Any | None = None,
107
+ **kwargs: Any,
108
+ ) -> QdrantDB:
109
+ """Factory helper to configure a Qdrant vector database instance.
110
+
111
+ Example:
112
+ ```python
113
+ from synapse.database import qdrant
114
+
115
+ # In-memory with MiniLM:
116
+ db = qdrant(embedding="minilm")
117
+
118
+ # Local disk with FastEmbed BGE:
119
+ db = qdrant(path="data/qdrant_db", embedding="fastembed")
120
+
121
+ # Remote cluster:
122
+ db = qdrant(url="https://xyz.cloud.qdrant.io", api_key="...", embedding="minilm")
123
+ ```
124
+ """
125
+ return QdrantDB(
126
+ location=location,
127
+ url=url,
128
+ api_key=api_key,
129
+ path=path,
130
+ collection_name=collection_name,
131
+ embedding=embedding,
132
+ embedding_model=embedding_model,
133
+ embedding_function=embedding_function,
134
+ **kwargs,
135
+ )
136
+
137
+
138
+ __all__ = ["QdrantDB", "qdrant"]
@@ -0,0 +1,21 @@
1
+ """Embedding model providers and resolution utilities for Synapse."""
2
+
3
+ from synapse.embeddings.base import (
4
+ EmbeddingModelRegistry,
5
+ fastembed,
6
+ huggingface_embeddings,
7
+ minilm,
8
+ ollama_embeddings,
9
+ openai_embeddings,
10
+ resolve_embedding,
11
+ )
12
+
13
+ __all__ = [
14
+ "EmbeddingModelRegistry",
15
+ "fastembed",
16
+ "huggingface_embeddings",
17
+ "minilm",
18
+ "ollama_embeddings",
19
+ "openai_embeddings",
20
+ "resolve_embedding",
21
+ ]
@@ -0,0 +1,262 @@
1
+ """Pluggable embedding providers and model resolution engine for Synapse."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from synapse.config import DEFAULT_EMBEDDING_MODEL
8
+
9
+
10
+ class EmbeddingModelRegistry:
11
+ """Friendly shorthand aliases for popular embedding models."""
12
+
13
+ ALIASES: dict[str, str] = {
14
+ # MiniLM variants
15
+ "minilm": "sentence-transformers/all-MiniLM-L6-v2",
16
+ "all-minilm": "sentence-transformers/all-MiniLM-L6-v2",
17
+ "all-minilm-l6-v2": "sentence-transformers/all-MiniLM-L6-v2",
18
+ "minilm-l6": "sentence-transformers/all-MiniLM-L6-v2",
19
+ "paraphrase-minilm": "sentence-transformers/paraphrase-MiniLM-L6-v2",
20
+ # BGE variants (Default FastEmbed models)
21
+ "fastembed": DEFAULT_EMBEDDING_MODEL,
22
+ "bge": DEFAULT_EMBEDDING_MODEL,
23
+ "bge-small": "BAAI/bge-small-en-v1.5",
24
+ "bge-base": "BAAI/bge-base-en-v1.5",
25
+ "bge-large": "BAAI/bge-large-en-v1.5",
26
+ # Nomic
27
+ "nomic": "nomic-ai/nomic-embed-text-v1.5",
28
+ "nomic-embed": "nomic-ai/nomic-embed-text-v1.5",
29
+ # Multilingual E5
30
+ "e5": "intfloat/multilingual-e5-large",
31
+ "multilingual-e5": "intfloat/multilingual-e5-large",
32
+ # OpenAI models
33
+ "openai": "text-embedding-3-small",
34
+ "text-embedding-3-small": "text-embedding-3-small",
35
+ "text-embedding-3-large": "text-embedding-3-large",
36
+ "ada": "text-embedding-ada-002",
37
+ "text-embedding-ada-002": "text-embedding-ada-002",
38
+ }
39
+
40
+
41
+ def fastembed(
42
+ model_name: str = "BAAI/bge-small-en-v1.5",
43
+ max_length: int | None = None,
44
+ **kwargs: Any,
45
+ ) -> Any:
46
+ """Initializes FastEmbed ONNX local embeddings (ultra-fast, zero-PyTorch).
47
+
48
+ Args:
49
+ model_name: Model name or HuggingFace ID (e.g. 'BAAI/bge-small-en-v1.5',
50
+ 'sentence-transformers/all-MiniLM-L6-v2', 'nomic-ai/nomic-embed-text-v1.5').
51
+ max_length: Optional maximum sequence length.
52
+ **kwargs: Extra arguments forwarded to FastEmbedEmbeddings.
53
+
54
+ Returns:
55
+ FastEmbedEmbeddings instance.
56
+ """
57
+ resolved_model = EmbeddingModelRegistry.ALIASES.get(model_name.lower(), model_name)
58
+ try:
59
+ from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
60
+
61
+ extra_args: dict[str, Any] = dict(kwargs)
62
+ if max_length is not None:
63
+ extra_args["max_length"] = max_length
64
+ return FastEmbedEmbeddings(model_name=resolved_model, **extra_args)
65
+ except Exception as e:
66
+ raise RuntimeError(
67
+ f"Failed to load FastEmbed model '{resolved_model}': {e}. "
68
+ "Ensure 'fastembed' is installed."
69
+ ) from e
70
+
71
+
72
+ def minilm(
73
+ model_name: str = "sentence-transformers/all-MiniLM-L6-v2",
74
+ **kwargs: Any,
75
+ ) -> Any:
76
+ """Convenience factory for MiniLM embeddings via FastEmbed ONNX runtime.
77
+
78
+ Args:
79
+ model_name: MiniLM model variant. Defaults to 'sentence-transformers/all-MiniLM-L6-v2'.
80
+ **kwargs: Extra parameters passed to FastEmbed.
81
+
82
+ Returns:
83
+ FastEmbedEmbeddings configured with all-MiniLM-L6-v2.
84
+ """
85
+ return fastembed(model_name=model_name, **kwargs)
86
+
87
+
88
+ def openai_embeddings(
89
+ model: str = "text-embedding-3-small",
90
+ api_key: str | None = None,
91
+ **kwargs: Any,
92
+ ) -> Any:
93
+ """Initializes OpenAI cloud vector embeddings.
94
+
95
+ Args:
96
+ model: OpenAI embedding model name. Defaults to 'text-embedding-3-small'.
97
+ api_key: Optional OpenAI API key (reads OPENAI_API_KEY env if omitted).
98
+ **kwargs: Extra parameters passed to OpenAIEmbeddings.
99
+
100
+ Returns:
101
+ OpenAIEmbeddings instance.
102
+ """
103
+ try:
104
+ from langchain_openai import OpenAIEmbeddings
105
+
106
+ return OpenAIEmbeddings(model=model, api_key=api_key, **kwargs)
107
+ except ImportError:
108
+ try:
109
+ from langchain_community.embeddings import OpenAIEmbeddings
110
+
111
+ return OpenAIEmbeddings(model=model, openai_api_key=api_key, **kwargs)
112
+ except Exception as e:
113
+ raise ImportError(
114
+ "OpenAI embeddings require 'langchain-openai'. "
115
+ "Install it via 'pip install langchain-openai'."
116
+ ) from e
117
+
118
+
119
+ def ollama_embeddings(
120
+ model: str = "nomic-embed-text",
121
+ base_url: str = "http://localhost:11434",
122
+ **kwargs: Any,
123
+ ) -> Any:
124
+ """Initializes Ollama local embeddings.
125
+
126
+ Args:
127
+ model: Model tag installed in Ollama (e.g. 'nomic-embed-text', 'all-minilm').
128
+ base_url: Ollama daemon URL. Defaults to 'http://localhost:11434'.
129
+ **kwargs: Extra parameters.
130
+
131
+ Returns:
132
+ OllamaEmbeddings instance.
133
+ """
134
+ try:
135
+ from langchain_community.embeddings import OllamaEmbeddings
136
+
137
+ return OllamaEmbeddings(model=model, base_url=base_url, **kwargs)
138
+ except Exception as e:
139
+ raise ImportError(
140
+ f"Failed to load Ollama embeddings: {e}. "
141
+ "Ensure 'langchain-community' and Ollama are available."
142
+ ) from e
143
+
144
+
145
+ def huggingface_embeddings(
146
+ model_name: str = "sentence-transformers/all-MiniLM-L6-v2",
147
+ **kwargs: Any,
148
+ ) -> Any:
149
+ """Initializes PyTorch HuggingFace sentence-transformers embeddings.
150
+
151
+ Args:
152
+ model_name: HuggingFace model repo name.
153
+ **kwargs: Extra parameters.
154
+
155
+ Returns:
156
+ HuggingFaceEmbeddings instance.
157
+ """
158
+ try:
159
+ from langchain_community.embeddings import HuggingFaceEmbeddings
160
+
161
+ return HuggingFaceEmbeddings(model_name=model_name, **kwargs)
162
+ except Exception:
163
+ # Fall back to FastEmbed for identical weights with 0 PyTorch
164
+ return fastembed(model_name=model_name, **kwargs)
165
+
166
+
167
+ def resolve_embedding(
168
+ embedding: str | Any | None = None,
169
+ embedding_model: str | None = None,
170
+ embedding_function: Any | None = None,
171
+ **kwargs: Any,
172
+ ) -> Any:
173
+ """Flexible resolver converting strings, aliases, or objects into an embedding instance.
174
+
175
+ Supported patterns:
176
+ - None: Defaults to 'BAAI/bge-small-en-v1.5' via FastEmbed
177
+ - 'minilm' or 'all-minilm-l6-v2': MiniLM ONNX embedding
178
+ - 'fastembed' or 'bge-small': BGE Small ONNX embedding
179
+ - 'openai' or 'text-embedding-3-small': OpenAI cloud embedding
180
+ - 'ollama' or 'nomic-embed-text': Local Ollama embedding
181
+ - Custom Embeddings object: Returned as-is
182
+
183
+ Args:
184
+ embedding: Model alias string, HuggingFace ID, or Embeddings instance.
185
+ embedding_model: Backward-compatible alias for embedding string.
186
+ embedding_function: Backward-compatible alias for custom object.
187
+ **kwargs: Extra keyword arguments forwarded to the provider.
188
+
189
+ Returns:
190
+ An initialized embedding function satisfying the LangChain Embeddings protocol.
191
+ """
192
+ # 1. Custom pre-instantiated object passed
193
+ target = embedding if embedding is not None else (embedding_function or embedding_model)
194
+
195
+ if target is None:
196
+ return fastembed(DEFAULT_EMBEDDING_MODEL, **kwargs)
197
+
198
+ # Check if target is already an embedding instance (has embed_documents / embed_query)
199
+ if hasattr(target, "embed_documents") and hasattr(target, "embed_query"):
200
+ return target
201
+
202
+ # Callable function wrapper
203
+ if callable(target) and not isinstance(target, str):
204
+ return target
205
+
206
+ # 2. String alias resolution
207
+ if isinstance(target, str):
208
+ clean_str = target.strip()
209
+ lower_str = clean_str.lower()
210
+
211
+ # MiniLM shorthand
212
+ if lower_str in ("minilm", "all-minilm", "all-minilm-l6-v2", "minilm-l6", "sentence-transformers/all-minilm-l6-v2"):
213
+ return minilm(**kwargs)
214
+
215
+ # FastEmbed / BGE shorthands
216
+ if lower_str in ("fastembed", "bge", "bge-small", "baai/bge-small-en-v1.5"):
217
+ return fastembed("BAAI/bge-small-en-v1.5", **kwargs)
218
+
219
+ if lower_str in ("bge-base", "baai/bge-base-en-v1.5"):
220
+ return fastembed("BAAI/bge-base-en-v1.5", **kwargs)
221
+
222
+ if lower_str in ("bge-large", "baai/bge-large-en-v1.5"):
223
+ return fastembed("BAAI/bge-large-en-v1.5", **kwargs)
224
+
225
+ if lower_str in ("nomic", "nomic-embed", "nomic-ai/nomic-embed-text-v1.5"):
226
+ return fastembed("nomic-ai/nomic-embed-text-v1.5", **kwargs)
227
+
228
+ if lower_str in ("e5", "multilingual-e5", "intfloat/multilingual-e5-large"):
229
+ return fastembed("intfloat/multilingual-e5-large", **kwargs)
230
+
231
+ # OpenAI shorthands
232
+ if lower_str in ("openai", "text-embedding-3-small", "text-embedding-3-large", "ada", "text-embedding-ada-002"):
233
+ model_name = "text-embedding-3-small" if lower_str == "openai" else clean_str
234
+ return openai_embeddings(model=model_name, **kwargs)
235
+
236
+ # Ollama shorthands
237
+ if lower_str.startswith("ollama:"):
238
+ ollama_m = clean_str.split(":", 1)[1]
239
+ return ollama_embeddings(model=ollama_m, **kwargs)
240
+
241
+ if lower_str == "ollama":
242
+ return ollama_embeddings(**kwargs)
243
+
244
+ # Try FastEmbed with the arbitrary model name (supports HuggingFace repo names)
245
+ try:
246
+ return fastembed(clean_str, **kwargs)
247
+ except Exception:
248
+ # Fall back to HuggingFace Embeddings
249
+ return huggingface_embeddings(clean_str, **kwargs)
250
+
251
+ raise ValueError(f"Unsupported embedding specification: {target!r}")
252
+
253
+
254
+ __all__ = [
255
+ "EmbeddingModelRegistry",
256
+ "fastembed",
257
+ "huggingface_embeddings",
258
+ "minilm",
259
+ "ollama_embeddings",
260
+ "openai_embeddings",
261
+ "resolve_embedding",
262
+ ]
@@ -0,0 +1,14 @@
1
+ """Synapse exporters for Model Context Protocol (MCP) schemas and 3D knowledge graphs."""
2
+
3
+ from synapse.export.mcp_exporter import MCPExporter, mcpexporter
4
+ from synapse.storage.graph_exporter import GraphExporter
5
+
6
+ # Developer-friendly alias
7
+ graphexporter = GraphExporter
8
+
9
+ __all__ = [
10
+ "GraphExporter",
11
+ "MCPExporter",
12
+ "graphexporter",
13
+ "mcpexporter",
14
+ ]
@@ -0,0 +1,202 @@
1
+ """MCP (Model Context Protocol) tool schema, server runner, and configuration exporter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ try:
12
+ from mcp.server.mcpserver import MCPServer
13
+ except ImportError:
14
+ MCPServer = None # type: ignore
15
+
16
+
17
+ class MCPExporter:
18
+ """Exports Synapse connectors and tools to standard MCP schemas, client configs, and stdio servers."""
19
+
20
+ @classmethod
21
+ def to_tools_list(cls, source: Any) -> list[dict[str, Any]]:
22
+ """Extracts standard MCP tool definitions from BaseApp or list of connectors."""
23
+ connectors = cls._resolve_connectors(source)
24
+ tools_list: list[dict[str, Any]] = []
25
+
26
+ for conn in connectors:
27
+ c_name = getattr(conn, "name", "custom_mcp")
28
+ c_desc = getattr(conn, "description", "")
29
+ raw_tools = (
30
+ getattr(conn, "tools", None)
31
+ or getattr(conn, "tools_dict", None)
32
+ or {}
33
+ )
34
+
35
+ if isinstance(raw_tools, dict) and raw_tools:
36
+ for t_name, t_func in raw_tools.items():
37
+ doc = getattr(t_func, "__doc__", "") or f"{t_name} tool provided by {c_name}"
38
+ tools_list.append({
39
+ "name": f"{c_name}_{t_name}" if c_name not in t_name else t_name,
40
+ "description": doc.strip(),
41
+ "server": c_name,
42
+ "inputSchema": {
43
+ "type": "object",
44
+ "properties": {
45
+ "query": {"type": "string", "description": "Search query or input parameter"}
46
+ },
47
+ },
48
+ })
49
+ elif hasattr(conn, "default_tool") and conn.default_tool:
50
+ tools_list.append({
51
+ "name": f"{c_name}_{conn.default_tool}",
52
+ "description": c_desc or f"Tool for {c_name}",
53
+ "server": c_name,
54
+ "inputSchema": {
55
+ "type": "object",
56
+ "properties": {
57
+ "query": {"type": "string", "description": "Search query or input parameter"}
58
+ },
59
+ },
60
+ })
61
+
62
+ return tools_list
63
+
64
+ @classmethod
65
+ def create_mcp_server(
66
+ cls,
67
+ source: Any,
68
+ name: str | None = None,
69
+ ) -> Any:
70
+ """Instantiates an official `mcp.server.mcpserver.MCPServer` wired with connector tools."""
71
+ if MCPServer is None:
72
+ raise ImportError(
73
+ "The 'mcp' package is required to create an MCP server. "
74
+ "Install it with: pip install 'mcp>=2.3.0'"
75
+ )
76
+
77
+ server_name = name or getattr(source, "title", getattr(source, "name", "synapse"))
78
+ server = MCPServer(name=server_name)
79
+
80
+ connectors = cls._resolve_connectors(source)
81
+ for conn in connectors:
82
+ c_name = getattr(conn, "name", "tool")
83
+ raw_tools = (
84
+ getattr(conn, "tools", None)
85
+ or getattr(conn, "tools_dict", None)
86
+ or {}
87
+ )
88
+
89
+ if isinstance(raw_tools, dict) and raw_tools:
90
+ for t_name, t_func in raw_tools.items():
91
+ doc = getattr(t_func, "__doc__", "") or f"Execute {t_name} via {c_name}"
92
+ t_alias = f"{c_name}_{t_name}" if c_name not in t_name else t_name
93
+ server.add_tool(t_func, name=t_name, description=doc.strip())
94
+ if t_alias != t_name:
95
+ try:
96
+ server.add_tool(t_func, name=t_alias, description=doc.strip())
97
+ except Exception:
98
+ pass # nosec B110
99
+ elif hasattr(conn, "call_tool"):
100
+ default_t = getattr(conn, "default_tool", "search")
101
+ doc = getattr(conn, "description", f"Search {c_name}")
102
+
103
+ def _make_adapter(c_instance: Any, t_action: str, doc_str: str) -> Callable[..., Any]:
104
+ async def dynamic_tool(query: str = "", **kwargs: Any) -> str:
105
+ args = {"query": query, **kwargs} if query else kwargs
106
+ res = await c_instance.call_tool(t_action, args)
107
+ if isinstance(res, (dict, list)):
108
+ return json.dumps(res, indent=2)
109
+ return str(res)
110
+
111
+ dynamic_tool.__name__ = f"{c_instance.name}_{t_action}"
112
+ dynamic_tool.__doc__ = doc_str
113
+ return dynamic_tool
114
+
115
+ adapter = _make_adapter(conn, default_t, doc)
116
+ server.add_tool(adapter, name=f"{c_name}_{default_t}", description=doc)
117
+
118
+ return server
119
+
120
+ @classmethod
121
+ def run_stdio(
122
+ cls,
123
+ source: Any,
124
+ name: str | None = None,
125
+ ) -> None:
126
+ """Runs the MCP server over standard I/O (stdio) for Claude Desktop or Cursor."""
127
+ server = cls.create_mcp_server(source, name=name)
128
+ server.run(transport="stdio")
129
+
130
+ @classmethod
131
+ def to_claude_desktop_config(
132
+ cls,
133
+ source: Any,
134
+ server_name: str = "synapse",
135
+ python_path: str | None = None,
136
+ args: list[str] | None = None,
137
+ host: str = "127.0.0.1",
138
+ port: int = 8000,
139
+ ) -> dict[str, Any]:
140
+ """Generates standard claude_desktop_config.json configuration for Claude Desktop."""
141
+ cmd = python_path or sys.executable or "python3"
142
+ run_args = args or ["-m", "synapse.cli", "mcp"]
143
+ return {
144
+ "mcpServers": {
145
+ server_name: {
146
+ "command": cmd,
147
+ "args": run_args,
148
+ "env": {
149
+ "SYNAPSE_HOST": host,
150
+ "SYNAPSE_PORT": str(port),
151
+ },
152
+ }
153
+ }
154
+ }
155
+
156
+ @classmethod
157
+ def to_json(cls, source: Any, indent: int = 2) -> str:
158
+ """Serializes all tool schemas and server definitions into a JSON string."""
159
+ payload = {
160
+ "mcpServers": cls.to_claude_desktop_config(source)["mcpServers"],
161
+ "tools": cls.to_tools_list(source),
162
+ }
163
+ return json.dumps(payload, indent=indent)
164
+
165
+ @classmethod
166
+ def save_claude_desktop_config(
167
+ cls,
168
+ source: Any,
169
+ filepath: str | Path = "claude_desktop_config.json",
170
+ server_name: str = "synapse",
171
+ ) -> Path:
172
+ """Writes Claude Desktop configuration file to disk."""
173
+ target = Path(filepath)
174
+ target.parent.mkdir(parents=True, exist_ok=True)
175
+ config = cls.to_claude_desktop_config(source, server_name=server_name)
176
+ target.write_text(json.dumps(config, indent=2), encoding="utf-8")
177
+ return target
178
+
179
+ @staticmethod
180
+ def _resolve_connectors(source: Any) -> list[Any]:
181
+ """Extracts connector objects from BaseApp, lists, or single connector instances."""
182
+ if hasattr(source, "list_connectors"):
183
+ return source.list_connectors()
184
+ if hasattr(source, "_connectors"):
185
+ return list(source._connectors.values())
186
+ if isinstance(source, (list, tuple)):
187
+ return list(source)
188
+ if isinstance(source, dict):
189
+ return list(source.values())
190
+ if (
191
+ hasattr(source, "call_tool")
192
+ or hasattr(source, "tools_dict")
193
+ or hasattr(source, "default_tool")
194
+ ):
195
+ return [source]
196
+ return []
197
+
198
+
199
+ # Developer-friendly alias
200
+ mcpexporter = MCPExporter
201
+
202
+ __all__ = ["MCPExporter", "mcpexporter"]
@@ -0,0 +1,21 @@
1
+ """Pluggable knowledge graph backends for Synapse."""
2
+
3
+ from synapse.graph.base import BaseGraphStore
4
+ from synapse.graph.memory import InMemoryGraph, in_memory_graph
5
+ from synapse.graph.neo4j_store import Neo4jGraph, neo4j
6
+ from synapse.graph.networkx_store import PALETTE, NetworkXGraph, networkx
7
+
8
+ # Backwards compatible alias
9
+ PersistentGraphStore = NetworkXGraph
10
+
11
+ __all__ = [
12
+ "PALETTE",
13
+ "BaseGraphStore",
14
+ "InMemoryGraph",
15
+ "Neo4jGraph",
16
+ "NetworkXGraph",
17
+ "PersistentGraphStore",
18
+ "in_memory_graph",
19
+ "neo4j",
20
+ "networkx",
21
+ ]