Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,385 @@
|
||||
"""Project Knowledge connector surface: read-only, scoped, source-backed MCP contracts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from packages.connector_kit import (
|
||||
ConnectorContext,
|
||||
InMemoryKnowledgeIndex,
|
||||
PageRequest,
|
||||
ResourcePublication,
|
||||
SourceType,
|
||||
ToolPublication,
|
||||
decode_cursor,
|
||||
encode_cursor,
|
||||
)
|
||||
from packages.connector_kit.knowledge import DocumentNotFound
|
||||
from packages.hub_policy import Governance, governed_tool
|
||||
|
||||
|
||||
class KnowledgeConnector:
|
||||
"""Expose only registered project documents; no physical path is an input."""
|
||||
|
||||
connector_type = "knowledge"
|
||||
version = "1.0.0"
|
||||
|
||||
def __init__(self, index: InMemoryKnowledgeIndex, governance: Governance | None = None) -> None:
|
||||
self.index = index
|
||||
self.governance = governance
|
||||
|
||||
def _envelope(
|
||||
self,
|
||||
context: ConnectorContext,
|
||||
data: Any,
|
||||
*,
|
||||
truncated: bool = False,
|
||||
next_cursor: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"data": data,
|
||||
"meta": {
|
||||
"project": context.project_slug,
|
||||
"environment": context.environment,
|
||||
"source": "knowledge",
|
||||
"truncated": truncated,
|
||||
"nextCursor": next_cursor,
|
||||
"generatedAt": datetime.now(UTC).isoformat(),
|
||||
},
|
||||
"warnings": [],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _hit_payload(item: Any) -> dict[str, Any]:
|
||||
return {
|
||||
"uri": item.uri,
|
||||
"title": item.title,
|
||||
"path": item.path,
|
||||
"snippet": item.snippet,
|
||||
"sourceType": item.source_type,
|
||||
"sourceRevision": item.source_revision,
|
||||
"repositoryRef": item.repository_ref,
|
||||
"repositoryCommit": item.repository_commit,
|
||||
"contentHash": item.content_hash,
|
||||
"modifiedAt": item.modified_at.isoformat(),
|
||||
"score": item.score,
|
||||
"truncated": item.truncated,
|
||||
}
|
||||
|
||||
@governed_tool("knowledge.document.search")
|
||||
def document_search(
|
||||
self,
|
||||
context: ConnectorContext,
|
||||
query: str,
|
||||
*,
|
||||
source_types: Iterable[SourceType] = (),
|
||||
page: PageRequest | None = None,
|
||||
) -> dict[str, Any]:
|
||||
request = page or PageRequest()
|
||||
result = self.index.search(
|
||||
context.project_id,
|
||||
query,
|
||||
source_types=source_types,
|
||||
limit=min(request.limit, 20),
|
||||
cursor=request.cursor,
|
||||
)
|
||||
data = {
|
||||
"items": [self._hit_payload(item) for item in result.items],
|
||||
"nextCursor": result.next_cursor,
|
||||
"truncated": result.truncated,
|
||||
}
|
||||
return self._envelope(
|
||||
context, data, truncated=result.truncated, next_cursor=result.next_cursor
|
||||
)
|
||||
|
||||
@governed_tool("knowledge.document.get")
|
||||
def document_get(
|
||||
self, context: ConnectorContext, uri: str, *, max_bytes: int = 200_000
|
||||
) -> dict[str, Any]:
|
||||
if not 1 <= max_bytes <= 262_144:
|
||||
raise ValueError("max_bytes is outside the connector output budget")
|
||||
document = self.index.get_document(context.project_id, uri)
|
||||
encoded = document.content.encode("utf-8")
|
||||
truncated = len(encoded) > max_bytes
|
||||
content = (
|
||||
encoded[:max_bytes].decode("utf-8", errors="ignore") if truncated else document.content
|
||||
)
|
||||
data = {
|
||||
"uri": document.uri,
|
||||
"title": document.title,
|
||||
"mimeType": document.mime_type,
|
||||
"content": content,
|
||||
"path": document.external_ref,
|
||||
"sourceType": document.source_type,
|
||||
"sourceRevision": document.source_revision,
|
||||
"repositoryRef": document.repository_ref,
|
||||
"repositoryCommit": document.repository_commit,
|
||||
"contentHash": document.content_hash,
|
||||
"modifiedAt": document.modified_at.isoformat(),
|
||||
"truncated": truncated,
|
||||
}
|
||||
return self._envelope(context, data, truncated=truncated)
|
||||
|
||||
@governed_tool("knowledge.source.list")
|
||||
def source_list(
|
||||
self,
|
||||
context: ConnectorContext,
|
||||
*,
|
||||
source_type: SourceType | None = None,
|
||||
page: PageRequest | None = None,
|
||||
) -> dict[str, Any]:
|
||||
request = page or PageRequest(limit=25)
|
||||
sources = [
|
||||
source
|
||||
for source in self.index.list_sources(context.project_id)
|
||||
if source_type is None or source.source_type == source_type
|
||||
]
|
||||
cursor = decode_cursor(
|
||||
request.cursor, str(context.project_id), "sources", str(source_type or "")
|
||||
)
|
||||
items = sources[cursor.offset : cursor.offset + min(request.limit, 100)]
|
||||
offset = cursor.offset + len(items)
|
||||
next_cursor = (
|
||||
encode_cursor(offset, str(context.project_id), "sources", str(source_type or ""))
|
||||
if offset < len(sources)
|
||||
else None
|
||||
)
|
||||
data = {
|
||||
"items": [
|
||||
{
|
||||
"sourceRef": item.source_ref,
|
||||
"sourceType": item.source_type,
|
||||
"uri": item.uri,
|
||||
"status": item.status,
|
||||
"sourceRevision": item.source_revision,
|
||||
"lastIndexedAt": item.last_indexed_at.isoformat()
|
||||
if item.last_indexed_at
|
||||
else None,
|
||||
"documentCount": item.document_count,
|
||||
}
|
||||
for item in items
|
||||
],
|
||||
"nextCursor": next_cursor,
|
||||
"truncated": next_cursor is not None,
|
||||
}
|
||||
return self._envelope(
|
||||
context, data, truncated=next_cursor is not None, next_cursor=next_cursor
|
||||
)
|
||||
|
||||
@governed_tool("knowledge.index.status")
|
||||
def index_status(self, context: ConnectorContext) -> dict[str, Any]:
|
||||
status = self.index.status(context.project_id)
|
||||
data = {
|
||||
"status": status.status,
|
||||
"sourceCount": status.source_count,
|
||||
"documentCount": status.document_count,
|
||||
"lastCompletedAt": status.last_completed_at.isoformat()
|
||||
if status.last_completed_at
|
||||
else None,
|
||||
"lastError": status.last_error,
|
||||
"generatedAt": datetime.now(UTC),
|
||||
}
|
||||
return self._envelope(context, data)
|
||||
|
||||
@governed_tool("knowledge.resource.get")
|
||||
def resource_get(self, context: ConnectorContext, uri: str) -> dict[str, Any]:
|
||||
if not uri.startswith("itworx-docs://"):
|
||||
raise DocumentNotFound("resource URI is not registered")
|
||||
return self.document_get(context, uri)
|
||||
|
||||
@governed_tool("knowledge.adr.list")
|
||||
def adr_list(
|
||||
self, context: ConnectorContext, *, page: PageRequest | None = None
|
||||
) -> dict[str, Any]:
|
||||
request = page or PageRequest(limit=25)
|
||||
documents = self.index.documents_for_source(context.project_id, SourceType.ADR)
|
||||
cursor = decode_cursor(request.cursor, str(context.project_id), "adr")
|
||||
items = documents[cursor.offset : cursor.offset + min(request.limit, 100)]
|
||||
offset = cursor.offset + len(items)
|
||||
next_cursor = (
|
||||
encode_cursor(offset, str(context.project_id), "adr")
|
||||
if offset < len(documents)
|
||||
else None
|
||||
)
|
||||
data = {
|
||||
"items": [
|
||||
{
|
||||
"uri": doc.uri,
|
||||
"number": doc.external_ref,
|
||||
"title": doc.title,
|
||||
"status": "indexed",
|
||||
"updatedAt": doc.modified_at.isoformat(),
|
||||
}
|
||||
for doc in items
|
||||
],
|
||||
"nextCursor": next_cursor,
|
||||
"truncated": next_cursor is not None,
|
||||
}
|
||||
return self._envelope(
|
||||
context, data, truncated=next_cursor is not None, next_cursor=next_cursor
|
||||
)
|
||||
|
||||
@governed_tool("knowledge.project.summary")
|
||||
def project_summary(self, context: ConnectorContext) -> dict[str, Any]:
|
||||
status = self.index.status(context.project_id)
|
||||
sources = [source.uri for source in self.index.list_sources(context.project_id)]
|
||||
summary = (
|
||||
f"{status.document_count} indexed documents across "
|
||||
f"{status.source_count} registered sources."
|
||||
)
|
||||
return self._envelope(
|
||||
context,
|
||||
{
|
||||
"summary": summary,
|
||||
"sources": sources,
|
||||
"sourceRevision": status.last_completed_at.isoformat()
|
||||
if status.last_completed_at
|
||||
else "never",
|
||||
"generatedAt": datetime.now(UTC),
|
||||
},
|
||||
)
|
||||
|
||||
def tools(self) -> tuple[ToolPublication, ...]:
|
||||
return KNOWLEDGE_TOOLS
|
||||
|
||||
def resources(self) -> tuple[ResourcePublication, ...]:
|
||||
return KNOWLEDGE_RESOURCES
|
||||
|
||||
def invoke(self, tool: str, context: ConnectorContext, **arguments: Any) -> dict[str, Any]:
|
||||
methods: dict[str, Callable[..., dict[str, Any]]] = {
|
||||
"knowledge.document.search": self.document_search,
|
||||
"knowledge.document.get": self.document_get,
|
||||
"knowledge.source.list": self.source_list,
|
||||
"knowledge.index.status": self.index_status,
|
||||
"knowledge.adr.list": self.adr_list,
|
||||
"knowledge.project.summary": self.project_summary,
|
||||
"knowledge.resource.get": self.resource_get,
|
||||
}
|
||||
method = methods.get(tool)
|
||||
if method is None:
|
||||
raise ValueError("unknown knowledge tool")
|
||||
return method(context, **arguments)
|
||||
|
||||
def health(self) -> dict[str, Any]:
|
||||
return {"status": "healthy", "component": "knowledge-connector"}
|
||||
|
||||
def readiness(self) -> dict[str, Any]:
|
||||
return {"status": "ready", "component": "knowledge-connector"}
|
||||
|
||||
|
||||
_COMMON_EMPTY_INPUT = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {},
|
||||
"required": [],
|
||||
}
|
||||
_COMMON_PAGE = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
|
||||
"cursor": {"type": ["string", "null"], "maxLength": 500},
|
||||
},
|
||||
"required": [],
|
||||
}
|
||||
_COMMON_OUTPUT = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"data": {"type": "object"},
|
||||
"meta": {"type": "object"},
|
||||
"warnings": {"type": "array"},
|
||||
},
|
||||
"required": ["data", "meta", "warnings"],
|
||||
}
|
||||
|
||||
KNOWLEDGE_TOOLS = (
|
||||
ToolPublication(
|
||||
canonical_name="knowledge.document.search",
|
||||
version="1.0.0",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"query": {"type": "string", "minLength": 1, "maxLength": 1000},
|
||||
"sourceTypes": {"type": "array", "maxItems": 8},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 20},
|
||||
"cursor": {"type": ["string", "null"], "maxLength": 500},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
output_schema=_COMMON_OUTPUT,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
ToolPublication(
|
||||
canonical_name="knowledge.document.get",
|
||||
version="1.0.0",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"properties": {
|
||||
"uri": {
|
||||
"type": "string",
|
||||
"pattern": "^itworx-docs://",
|
||||
"maxLength": 2048,
|
||||
}
|
||||
},
|
||||
"required": ["uri"],
|
||||
},
|
||||
output_schema=_COMMON_OUTPUT,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
ToolPublication(
|
||||
canonical_name="knowledge.source.list",
|
||||
version="1.0.0",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
input_schema=_COMMON_PAGE,
|
||||
output_schema=_COMMON_OUTPUT,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
ToolPublication(
|
||||
canonical_name="knowledge.index.status",
|
||||
version="1.0.0",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
input_schema=_COMMON_EMPTY_INPUT,
|
||||
output_schema=_COMMON_OUTPUT,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
ToolPublication(
|
||||
canonical_name="knowledge.adr.list",
|
||||
version="1.0.0",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
input_schema=_COMMON_PAGE,
|
||||
output_schema=_COMMON_OUTPUT,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
ToolPublication(
|
||||
canonical_name="knowledge.project.summary",
|
||||
version="1.0.0",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
input_schema=_COMMON_EMPTY_INPUT,
|
||||
output_schema=_COMMON_OUTPUT,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
)
|
||||
|
||||
KNOWLEDGE_RESOURCES = (
|
||||
ResourcePublication(
|
||||
uri_template="itworx-docs://{project}/{source}/{documentId}",
|
||||
connector_type="knowledge",
|
||||
risk_class="R1",
|
||||
mime_types=("text/markdown", "text/plain", "application/json"),
|
||||
max_bytes=262_144,
|
||||
required_scopes=("project:{project}:read",),
|
||||
),
|
||||
)
|
||||
Reference in new issue
Block a user