Services
The services of the MCPServer app: what it can do, for the rest of the platform.
The services know nothing of the Model Context Protocol. The MCPServer’s tools
(smarter.apps.mcpserver.protocol.tools) are thin adapters of them, and any other part of
the platform, e.g. the web console, a management command or another app, can call them too.
Import them from this package, which names the app’s capabilities, rather than from its modules.
The Smarter Project’s repositories, documentation and blog, from the platform’s own index of them:
repositories: the repositories and the blog that are indexed.indexing: indexes them from GitHub and from WordPress, every hour, and queues one that is not indexed yet.search: searches their documentation, code and posts.reading: reads a file, a folder, a post, or a repository’s overview.manifests: the JSON Schema and example manifest of each resource kind, and the platform’s own validation of a manifest.github: searches and reads GitHub issues, and opens issues and pull requests as the caller.
This platform’s operations, for its staff:
status: the platform’s health now, and the cluster as the cloud provider sees it.logs: the last 72 hours of the pod logs.helpdesk: one user’s account and problems, and the check of a reply to them.acting_user: who is chatting with an LLMClient, signed by the platform for its own staff-only endpoint. The prompt api and the MCPClient app use it.
A service raises SmarterMCPServerServiceError, or its
subclass SmarterMCPServerNotFoundError, with a message
written for the caller.
Note
Experimental. The MCPServer was designed and coded by Claude Code (Anthropic’s Claude Opus 5.5), with Lawrence McDaniel as co-author. It is experimental, and will be documented.
- exception smarter.apps.mcpserver.services.SmarterMCPServerNotFoundError(message, suggestions)[source]
Bases:
SmarterMCPServerServiceErrorA file, folder or blog post that was asked for does not exist, with what the caller may have meant.
- Parameters:
- exception smarter.apps.mcpserver.services.SmarterMCPServerServiceError(message='')[source]
Bases:
SmarterMCPServerExceptionA service could not do what it was asked, e.g. a file does not exist, or a repository is not indexed yet.
The services of
smarter.apps.mcpserver.servicesraise it, with a message that tells the caller what was wrong, and what to do instead, e.g. “Did you mean: …?”. They know nothing of MCP:smarter.apps.mcpserver.protocol.tools.public.PublicTool.call()andsmarter.apps.mcpserver.protocol.tools.staff.StaffTool.call()turn it into aSmarterMCPServerToolError, which the LLM reads as the tool’s result.
- smarter.apps.mcpserver.services.acting_user(user)[source]
Make a user the acting user of the MCP server requests that the block makes.
The value is a context variable, so it follows the code into
asgiref.sync.async_to_sync()andasgiref.sync.sync_to_async(), and is restored when the block ends.Example usage:
with acting_user(request.user): response = handler(user_profile, prompt, data)
- smarter.apps.mcpserver.services.acting_user_headers(url, user)[source]
Return the acting user header to send to an MCP server, if it is this platform’s staff-only endpoint.
- Parameters:
user (
User|None) – The acting user, usually thecurrent_acting_user()when the connection was created, or None for nobody in particular.
- Return type:
- Returns:
{ACTING_USER_HEADER: <signed value>}for this platform’s own staff-only endpoint, or an empty dict for any other MCP server, which never learns who is chatting.
- smarter.apps.mcpserver.services.check_reply(reply, requester_email=None)[source]
Find everything in a draft reply that breaks the reply rules.
- Parameters:
- Return type:
- Returns:
The findings, those of the Guardrail detectors first, at most
MAX_FINDINGS, or an empty list if none of the rules finds anything.
- smarter.apps.mcpserver.services.current_acting_user()[source]
Return the acting user that
acting_user()set, if any.- Return type:
- Returns:
The user, or None outside an
acting_user()block, or for an anonymous user.
- smarter.apps.mcpserver.services.eks_status()[source]
Read the cluster’s health from the cloud provider, and the recent AWS events of its region.
Side effects: read-only calls to the EKS api (
DescribeCluster,ListNodegroups,DescribeNodegroup,ListAddons,DescribeAddon,DescribeAddonVersionsandListInsights), and up to two HTTPS GETs of AWS status feeds, which are cached for a minute.- Return type:
- Returns:
The cluster’s health, and the region’s AWS events.
- Raises:
InfrastructureNotReadyError – If the cloud provider is not ready.
SmarterInfrastructureError – If the cluster cannot be described.
- smarter.apps.mcpserver.services.get_repository(name)[source]
Return one of
SMARTER_PROJECT_REPOSITORIES, by name.- Parameters:
name (
str) – The repository’s name, e.g.smarter-cli.- Return type:
- Returns:
The repository, or None if there is no such repository.
- smarter.apps.mcpserver.services.index_blog(force=False, blog=None)[source]
Bring the blog’s index up to date with its published posts.
If the posts’ fingerprint is already indexed, only a previous error is cleared. Two posts with the same path are indexed once, the newer one.
A failure never raises: the index keeps its posts, and stays ready if it was, so that the tools can go on answering from them, and the failure is recorded in its
error.Side effects: HTTP requests to the blog (see
fetch_posts()), database writes (the index’s row is created if it does not exist, and seestore_files()), and a signal:mcpserver_repository_indexedwhen the posts are replaced, whose receiver makes the cached tool results stale, ormcpserver_repository_index_failed.- Parameters:
force (
bool) – Index the posts even if they are already indexed.blog (
ProjectBlog|None) – The blog. Defaults toproject_blog().
- Return type:
- Returns:
The blog’s index. If indexing fails, its
errorsays why, and an existing index is kept.
Example usage:
index = index_blog() index.file_count # the number of posts
- smarter.apps.mcpserver.services.index_repository(name, force=False)[source]
Bring a repository’s index up to date with the latest commit of its branch.
If the latest commit is already indexed, only a previous error is cleared. Otherwise the archive is downloaded to a temporary file, which is deleted afterwards, and its files replace the index’s (see the module’s description for the steps).
A failure never raises: the index keeps its files, and stays ready if it was, so that the tools can go on answering from the previous commit, and the failure is recorded in its
error.Side effects: HTTP requests to GitHub (see
latest_commit_sha()anddownload_archive()), a temporary file, database writes (the index’s row is created if it does not exist, and seestore_files()), and a signal:mcpserver_repository_indexedwhen the files are replaced, whose receiver makes the cached tool results stale, ormcpserver_repository_index_failed.- Parameters:
- Return type:
- Returns:
The repository’s index. If indexing fails, its
errorsays why, and an existing index is kept.- Raises:
SmarterMCPServerIndexError – If there is no such repository.
Example usage:
index = index_repository("smarter-cli") index.status # 'ready'
- smarter.apps.mcpserver.services.is_staff_endpoint_url(url)[source]
Return whether a url is that of this platform’s own staff-only endpoint.
The scheme, host, port and path must all match
staff_endpoint_url(), with or without a trailing slash, so that a server elsewhere whose path looks the same never gets the header.
- smarter.apps.mcpserver.services.list_files(repository, folder=None)[source]
List the folders and indexed files directly in a folder of a repository.
Side effects: database queries, and, for a repository that is not indexed yet, a Celery task queued to index it.
- Parameters:
repository (
ProjectRepository) – The repository.folder (
str|None) – The folder, e.g.smarter/smarter/apps, or None for the top level.
- Return type:
- Returns:
The folder’s entries and GitHub url.
- Raises:
SmarterMCPServerServiceError – If the repository is not indexed yet.
SmarterMCPServerNotFoundError – If the folder has no indexed files.
- smarter.apps.mcpserver.services.log_errors(minutes, min_level, components=())[source]
Return the errors, or the warnings and errors, of the last minutes, grouped by their signature.
Side effects: one HTTP GET to Loki’s
query_range.- Parameters:
- Return type:
- Returns:
The number of lines, whether they were cut at
SUMMARY_LINES, and the groups.- Raises:
SmarterMCPServerServiceError – If the query is not valid.
LogServiceError – If Loki could not be reached, or rejected the query.
InfrastructureNotReadyError – If there is no log store.
- smarter.apps.mcpserver.services.log_sources(minutes)[source]
Return the components and pods that wrote to the logs in the last minutes.
Side effects: two HTTP GETs to Loki, for the values of the
componentandpodlabels.- Parameters:
minutes (
int) – The period, in minutes, up to now.- Return type:
- Returns:
The components, the components that wrote nothing, and the pods.
- Raises:
LogServiceError – If Loki could not be reached.
InfrastructureNotReadyError – If there is no log store.
- smarter.apps.mcpserver.services.platform_status(minutes)[source]
Read the platform’s health now, and what looks wrong.
Side effects: five
kubectl getcalls, aSELECT 1and twoSHOWqueries on the database, a value written to and deleted from the cache, a ping of the Celery workers, a connection to the Celery broker, and up to five HTTPS GETs of public status pages, which are cached for a minute. Nothing in the cluster, the database or the queues is changed.- Parameters:
minutes (
int) – How far back to read Kubernetes warning events.- Return type:
- Returns:
Everything that was read, and the observations. A part that cannot be read is named in the observations, and the others are still read.
- smarter.apps.mcpserver.services.project_blog()[source]
Return
SMARTER_PROJECT_BLOG.Every module reads the blog through this function, so that tests can replace it with a throwaway blog, whose index does not collide with the real one.
- Return type:
- Returns:
The blog.
- smarter.apps.mcpserver.services.project_repositories()[source]
Return
SMARTER_PROJECT_REPOSITORIES.Every module reads the repositories through this function, so that tests can replace them with throwaway repositories, whose indexes do not collide with the real ones.
- Return type:
- Returns:
The repositories, in the order that the MCP server lists them.
- smarter.apps.mcpserver.services.read_blog_post(path, start_line=1, end_line=None)[source]
Read a post of the blog, or a range of its lines, by its path or its url.
Side effects: database queries, and, if the blog is not indexed yet, a Celery task queued to index it.
- Parameters:
- Return type:
- Returns:
The lines, and the post’s url.
- Raises:
SmarterMCPServerServiceError – If the blog is not indexed yet.
SmarterMCPServerNotFoundError – If the blog has no such post. The error suggests the posts with the same slug.
- smarter.apps.mcpserver.services.read_file(repository, path, start_line=1, end_line=None)[source]
Read a file of a repository, or a range of its lines.
Side effects: database queries, and, for a repository that is not indexed yet, a Celery task queued to index it.
- Parameters:
repository (
ProjectRepository) – The repository.path (
str) – The file’s path in the repository, e.g.README.md. A leading slash is ignored.start_line (
int) – The first line to read, from 1.end_line (
int|None) – The last line to read, or None to read as many asread_lines()allows.
- Return type:
- Returns:
The lines, and the file’s GitHub url and documentation page.
- Raises:
SmarterMCPServerServiceError – If the repository is not indexed yet.
SmarterMCPServerNotFoundError – If the repository has no such file.
- smarter.apps.mcpserver.services.repositories_state(repositories)[source]
Return whether each repository is indexed, and queue those that are not for indexing.
Side effects: one database query, and a Celery task queued for each repository that is not indexed yet (see
queue_indexing()).- Parameters:
repositories (
Sequence[ProjectRepository]) – The repositories, in the order to return them.- Return type:
- Returns:
One state per repository, in the same order.
- smarter.apps.mcpserver.services.repository_overview(repository)[source]
Describe a repository: its indexed commit, its numbers of files, its top-level folders and files, and its README.
Side effects: database queries, and, for a repository that is not indexed yet, a Celery task queued to index it.
- Parameters:
repository (
ProjectRepository) – The repository.- Return type:
- Returns:
The overview.
- Raises:
SmarterMCPServerServiceError – If the repository is not indexed yet.
- smarter.apps.mcpserver.services.search_blog(query, limit)[source]
Search the posts of The Smarter Project’s blog.
Side effects: database queries, and, if the blog is not indexed yet, a Celery task queued to index it.
- Parameters:
- Return type:
- Returns:
The passages of the posts, best first.
- Raises:
SmarterMCPServerServiceError – If the blog is not indexed yet.
- smarter.apps.mcpserver.services.search_code(repositories, query, limit, path=None)[source]
Search the source code and configuration of the repositories.
Side effects: see
search_repositories().- Parameters:
- Return type:
- Returns:
The files, best first, with their matching lines, and the repositories that were not searched.
- Raises:
SmarterMCPServerServiceError – If none of the repositories is indexed yet.
- smarter.apps.mcpserver.services.search_documentation(repositories, query, limit)[source]
Search the documentation of the repositories: the Sphinx documentation, READMEs and other prose.
Side effects: see
search_repositories().- Parameters:
repositories (
Sequence[ProjectRepository]) – The repositories to search.query (
str) – The query, e.g.custom domain verification.limit (
int) – The maximum number of hits.
- Return type:
- Returns:
The passages, best first, and the repositories that were not searched.
- Raises:
SmarterMCPServerServiceError – If none of the repositories is indexed yet.
Example usage:
results = search_documentation([get_repository("smarter")], "custom domain verification", limit=8) results.hits[0].documentation_url
- smarter.apps.mcpserver.services.search_logs(minutes, limit, components=(), pod=None, contains=(), pattern=None, min_level=None, oldest_first=False)[source]
Return the lines of the last minutes that match every filter.
Side effects: one HTTP GET to Loki’s
query_range.- Parameters:
minutes (
int) – The period, in minutes, up to now.limit (
int) – The most lines to return.components (
tuple[str,...]) – The components to read, or an empty tuple for all of them.pod (
str|None) – One pod’s name, or None for every pod of the components.contains (
tuple[str,...]) – Texts that every line must contain, case insensitively.pattern (
str|None) – A regular expression, in RE2 syntax, that every line must match, or None.min_level (
LogLevel|None) – The least severe level to return, or None for lines of every level.oldest_first (
bool) – Whether to return the oldest lines of the period first, rather than the newest.
- Return type:
- Returns:
The lines, newest first unless
oldest_first.- Raises:
SmarterMCPServerServiceError – If the query is not valid, e.g. its pattern does not compile.
LogServiceError – If Loki could not be reached, or rejected the query.
InfrastructureNotReadyError – If there is no log store.
- smarter.apps.mcpserver.services.staff_endpoint_url()[source]
Return the url of this platform’s staff-only endpoint.
It is reversed against the platform’s urlconf, because a prompt may run on a deployed LLMClient’s host, whose urlconf has no MCPServer endpoints.
- Return type:
- Returns:
The url, e.g.
https://alpha.platform.example.com/api/v1/mcp/smarter-operations/.
- smarter.apps.mcpserver.services.support_snapshot(email, days=7)[source]
Gather what the platform knows about one user’s problem.
The user is found by their email address, case insensitively. Their failed sign-ins are searched for even when no user has the address, because someone may be trying to sign in with an address that is mistyped, or not registered.
Side effects: queries of the users, profiles, social sign-ins and journal, one query of the log store, and, on a hosted platform, a call to the Account Manager. Nothing is written.
- Parameters:
- Return type:
- Returns:
The snapshot. A part that cannot be read is named in its
errors.
- smarter.apps.mcpserver.services.validate_manifest_text(text)[source]
Validate a manifest that the LLM wrote, against the Pydantic model of its kind, without saving it.
The checks that make requests, e.g. that an image url responds, are skipped.
- Parameters:
text (
str) – The manifest, in YAML or JSON.- Return type:
- Returns:
Whether it is valid, and its errors.
- Raises:
SmarterMCPServerServiceError – If it is too long, or is not YAML.