Skip to content

[RSPEED-2267] Convert service commands to return structured output#225

Open
subpop wants to merge 10 commits into
rhel-lightspeed:mainfrom
subpop:convert-services-structured-output
Open

[RSPEED-2267] Convert service commands to return structured output#225
subpop wants to merge 10 commits into
rhel-lightspeed:mainfrom
subpop:convert-services-structured-output

Conversation

@subpop

@subpop subpop commented Jan 21, 2026

Copy link
Copy Markdown
Member

Refactor service-related tools to return structured data instead of plain text. This includes converting the output of list_services, get_service_status, and get_service_logs into JSON. Convert get_service_status to use systemctl show instead of the human-readably focused systemctl status. Implement a parser for the systemctl show command to facilitate this structured output.

@subpop subpop force-pushed the convert-services-structured-output branch 2 times, most recently from 980c7eb to f256b00 Compare January 28, 2026 14:11

@owtaylor owtaylor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure how to to approach turning systemctl status into structured objects:

A) Parse the output, turn that into objects
B) Use lower-level API to obtain the information, reimplemen* the "summarization" of systemctl status inside our code.
C) Expose the low-level data sources, let the model parse through what it needs

A) is ugly and fragile. B) is ugly. C) will definitely lead to underperformance.

B) is probably best ,but considering how well trained models are on actual systemctl status output, I'm a bit skeptical about structured objects at all as applied to this tool. (It's also possible to have a tool return structured content and freeform content ... but I don't think that really helps things here.)

Comment thread src/linux_mcp_server/commands.py Outdated
Comment thread src/linux_mcp_server/commands.py Outdated
@subpop

subpop commented Feb 25, 2026

Copy link
Copy Markdown
Member Author

I'm not quite sure how to to approach turning systemctl status into structured objects:

A) Parse the output, turn that into objects B) Use lower-level API to obtain the information, reimplemen* the "summarization" of systemctl status inside our code. C) Expose the low-level data sources, let the model parse through what it needs

A) is ugly and fragile. B) is ugly. C) will definitely lead to underperformance.

B) is probably best ,but considering how well trained models are on actual systemctl status output, I'm a bit skeptical about structured objects at all as applied to this tool. (It's also possible to have a tool return structured content and freeform content ... but I don't think that really helps things here.)

Should we bother returning structured content for this tool then? Maybe the best thing for this tool in particular is to return the true expected output of systemctl status instead of trying to find some sort of structured content. There is no rule that says every tool must return structured content.

@owtaylor

Copy link
Copy Markdown
Collaborator

Should we bother returning structured content for this tool then? Maybe the best thing for this tool in particular is to return the true expected output of systemctl status instead of trying to find some sort of structured content. There is no rule that says every tool must return structured content.

@samdoran - what do you think?

@samdoran

Copy link
Copy Markdown
Member

There are a couple of main things we want to accomplish:

  • Remove the format_* functions since the whole notion behind them is flawed and better handled by a model.
  • Validate the returned data and control the serialization of data returned from the tools when it makes sense.

These tools currently return nicely formatted text for display on a screen, but that text is never seen by a human. It may be helpful to send that formatted text to an LLM, but I doubt these specific formatting functions are swaying the accuracy of the LLM response and are therefore unnecessary.

One advantage of using Pydantic models for return types is that it provides validation of the returned data as well as control over how fields are serialized to JSON. For these particular tools, I don't think we need that capability since the output is mostly lines of text.

It may be the case that using Pydantic models for the return type, which is used to generate the return schema, may help the LLM make better use of the data that are returned, but I don't have a way to prove that.

For these tools, it's probably fine to just return the unaltered stdout.

@subpop

subpop commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

I can see a pattern where it makes sense to return the unaltered stdout from a command, but that makes the most sense when the calling model has decided which command to run. Since our tools are abstracting the commands away from the model, we should be returning the most predictable data structure to a "list_services" function. So by that reasoning, I can see how we should convert these tools. Making up a data structure for the output of systemctl status doesn't fully make sense to me though. 🤔

@subpop subpop force-pushed the convert-services-structured-output branch from f256b00 to 3da57ae Compare April 2, 2026 15:26
@subpop subpop requested a review from a team as a code owner April 2, 2026 15:26
@subpop subpop force-pushed the convert-services-structured-output branch from 3da57ae to 760e3c4 Compare April 2, 2026 15:48
@subpop subpop force-pushed the convert-services-structured-output branch from 760e3c4 to 5096992 Compare April 2, 2026 15:59
@codecov

codecov Bot commented Apr 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.57143% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/linux_mcp_server/tools/services.py 93.33% 1 Missing ⚠️
Flag Coverage Δ
unittests 94.30% <98.57%> (-3.12%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/linux_mcp_server/commands.py 89.47% <ø> (+0.38%) ⬆️
src/linux_mcp_server/formatters.py 95.14% <ø> (-0.19%) ⬇️
tests/test_formatters.py 100.00% <ø> (ø)
tests/tools/test_services.py 100.00% <100.00%> (ø)
src/linux_mcp_server/tools/services.py 96.00% <93.33%> (+16.00%) ⬆️

... and 16 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@subpop

subpop commented Apr 2, 2026

Copy link
Copy Markdown
Member Author

Ok. I patched up the get_service_status tool to return the plain string output of systemctl status and updated the related tests. I think this is ready for review again, finally.

So that tests can pass even when sshd.service is not installed.
@subpop subpop force-pushed the convert-services-structured-output branch from 62ee8df to df69257 Compare April 14, 2026 17:05
@github-actions

Copy link
Copy Markdown

For team members: test commit df69257 in internal GitLab

@subpop subpop requested a review from owtaylor April 14, 2026 18:21
@github-actions

Copy link
Copy Markdown

For team members: test commit ad53afd in internal GitLab

Modified the test for nonexistent services to ensure it correctly raises ToolError when no log entries are found.
@github-actions

Copy link
Copy Markdown

For team members: test commit d4b1d31 in internal GitLab

@owtaylor owtaylor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_service_logs() and get_service_status() look good to me, but I have a bit of a hesititation about list_services.

sub-state, and description.

Returns:
list[dict[str, str]]: A list of dictionaries containing information about each service.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the arguments I know of for doing the structured data conversion are:

A) Not having to maintain fragile formatters. Accomplished, though we could just also pass through the data.
B) Accomodating models that want to "code mode" transform the data programatically. Not accomplished - what is in this list[dict[str, str]]? (Maybe the model would first write a script to extract a few sample lines, but it's annoying to make them do it.)

I think we should do:

class ServiceStatus(BaseModel):
    unit: str
    load: str
    active: str
    sub: str
    description: str

service_status_list_adapter = TypeAdapter(List[User])
...

return service_status_list_adapter.validate_json(stdout)

(systemd documents: "The list of possible LOAD, ACTIVE, and SUB states is not constant and new systemd releases may both add and remove values", so we probably shouldn't try use enums - a competent model will understand these mysterious load/active/sub values in terms of its systemd knowledge)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants