Skip to content

Add Prompts support to the MCP Server #825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 28, 2025

Conversation

yasmewad
Copy link
Contributor

@yasmewad yasmewad commented Jul 25, 2025

NOTE: The main goal with the PR is to get feedback and discuss around ways to ingest the model / prompts appropriately.

This adds prompt support to the MCP server, letting LLMs get helpful context directly from Smithy models.

What's New

Core Features:
• Define prompts in Smithy using @prompts trait as defined in #829
• MCP server automatically exposes them via prompts/list and prompts/get
• Template support with {{placeholder}} syntax for dynamic content.

Key Components:
PromptLoader - discovers prompts from Smithy models
PromptProcessor - handles template substitution and validation

Testing

  • Added unit tests for various cases of PromptLoader, PromptProcessor and the MCP Server itself.
  • Added example in ProxyMcpServerExample and ran it with Q CLI.
  • Will add more testing examples in next revision after discussion has settled.

This is what /prompts in Q CLI which correctly demonstrates the data from the prompts trait. Will add more calling examples later.

Screenshot 2025-07-24 at 4 24 45 PM

User experience for prompts/get or @prompts invocation in Q CLI.

Screenshot 2025-07-24 at 6 21 09 PM


var prompt = prompts.get(promptName);

if (prompt == null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we respond with PromptMessage too ? Like we do in the file PromptProcessor.java?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you need model list here.

You should be able to pull this off the schema as long as these traits are retained at runtime. This controlled by runtimeTraits property, example

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for sharing that example it makes sense that we can use the schema. I will try that approach.

Copy link
Contributor

Choose a reason for hiding this comment

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

Should we make this a default runtime trait?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a valid point. Making it a default will save confusion at least for customers.

* @return List of PromptArgument objects representing the structure members
*/
public static List<PromptArgument> convertArgumentShapeToPromptArgument(Model model, ShapeId argumentShapeId) {
StructureShape argument = model.expectShape(argumentShapeId, StructureShape.class);
Copy link
Contributor Author

@yasmewad yasmewad Jul 25, 2025

Choose a reason for hiding this comment

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

Is there a way to get this behavior using the Service class? I looked at the TypeRegistry and Schema but could not find a pattern on how to use it. Maybe that's me being unfamiliar with access patterns of Smithy java.

Copy link
Contributor

Choose a reason for hiding this comment

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

You will not have access to the StructureShape, but in this method all you are doing is accessing members and traits. Both can be done from the Schema class, using Schema#getMembers and Schema#getTrait.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep I can try that out. Thanks.

Copy link
Contributor Author

@yasmewad yasmewad Jul 26, 2025

Choose a reason for hiding this comment

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

I tried to extract arguments using only the Schema and TypeRegistry from the Service interface, I hit some blockers:

Schema is too limited
It only knows about the main service shape and its direct pieces. It doesn't include operations or resources (where our @prompts traits actually live) a valid use-case. So we can't even find the prompts, let alone process them

TypeRegistry doesn't help. It only stores error types, nothing else. I tried to look up argument shapes, but they're not there

The main blocker is ShapeId references. Prompts have an arguments field that points to a structure (like SearchArguments). To process this, we need to find that structure in the model and read its fields Schema and TypeRegistry just don't have this information

What We're Doing Instead

We're parsing prompts in ProxyService because it has access to the full Smithy model. The prompts will behave as an operation with a synthetic trait on it. We will filter those in the McpServer from tools, and also serve them as prompts when needed. On a prompts/get call, we will just apply the arguments passed in by the client to the operation and return the template string. This is suggested by @rhernandez35 so that it is easier to pass the information by only passing the Service, and the ProxyService already has model information so we will be able to parse the information as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

It only knows about the main service shape and its direct pieces. It doesn't include operations or resources (where our @prompts traits actually live) a valid use-case. So we can't even find the prompts, let alone process them

This isn't accurate. We generate Schema for both operations as well as resources.

The main blocker is ShapeId references. Prompts have an arguments field that points to a structure (like SearchArguments). To process this, we need to find that structure in the model and read its fields Schema and TypeRegistry just don't have this information

Should be fixed by #827

Copy link
Contributor Author

@yasmewad yasmewad Jul 28, 2025

Choose a reason for hiding this comment

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

This isn't accurate. We generate Schema for both operations as well as resources.

Hmm, interesting from my debugging,maybe again this is my understanding and missing context. I was only able to the see the information related to the service. In my case, what would the PromptLoader method look like in terms of arguments it takes instead of the model we have currently?

Should be fixed by #827

Nice!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We're parsing prompts in ProxyService because it has access to the full Smithy model. The prompts will behave as an operation with a synthetic trait on it. We will filter those in the McpServer from tools, and also serve them as prompts when needed. On a prompts/get call, we will just apply the arguments passed in by the client to the operation and return the template string. This is suggested by @rhernandez35 so that it is easier to pass the information by only passing the Service, and the ProxyService already has model information so we will be able to parse the information as well.

Do you think this will be a good approach? I discussed with Richard and it seemed only fair for now.

In future, we should plan to move artifact generation for the tools and prompts such that the Smithy build system provides that data. The MCP Server can just become consumer using maybe a new McpService class that can take in Mcp specific things.

@adwsingh adwsingh force-pushed the mcp-prompts-support branch 2 times, most recently from 0e49455 to 3e97b6b Compare July 28, 2025 17:08
}

/**
* Applies template arguments to a template string with maximum efficiency.
Copy link
Contributor

Choose a reason for hiding this comment

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

"with maximum efficiency" doesn't need to be documented

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ack. Removed in local. Will send a new commit for various nit fixes.

.build())
.serverInfo(ServerInfo.builder()
.name(name)
.version("1.0.0")
.build())
.build());
case "prompts/list" -> writeResponse(req.getId(),
ListPromptsResult.builder().prompts(prompts.values().stream().toList()).build());
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to prevent the prompt templates from being serialized in this response. We only want the name, title, description, and arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I can update the response shape.

@adwsingh adwsingh force-pushed the mcp-prompts-support branch from 3e97b6b to d82e620 Compare July 28, 2025 19:08
@yasmewad yasmewad force-pushed the mcp-prompts-support branch 2 times, most recently from 49d5fc8 to aae765c Compare July 28, 2025 21:36
Comment on lines 62 to 65
return getServerJavaClassSymbol(serviceShape);
}

private Symbol getServerJavaClassSymbol() {
private Symbol getServerJavaClassSymbol(ServiceShape serviceShape) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't required. I think you messed something in the rebase.

@@ -22,6 +23,7 @@ public final class McpServerBuilder {
OutputStream os;
List<Service> serviceList = new ArrayList<>();
List<McpServerProxy> proxyList = new ArrayList<>();
List<Model> modelList = new ArrayList<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh yes. 🤦🏽‍♂️

@yasmewad yasmewad force-pushed the mcp-prompts-support branch from 212067c to f42538d Compare July 28, 2025 21:57
@yasmewad yasmewad merged commit 91eed0b into smithy-lang:main Jul 28, 2025
2 checks passed
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