Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ libraryDependencies += "com.tjclp" %% "fast-mcp-scala" % "0.2.0"
//> using dep com.tjclp::fast-mcp-scala:0.2.0
//> using options "-Xcheck-macros" "-experimental"

import com.tjclp.fastmcp.core.{Tool, ToolParam, Prompt, PromptParam, Resource, ResourceParam}
import com.tjclp.fastmcp.core.{Tool, Param, Prompt, Resource}
import com.tjclp.fastmcp.server.FastMcpServer
import com.tjclp.fastmcp.macros.RegistrationMacro.*
import zio.*
Expand All @@ -32,19 +32,19 @@ import zio.*
object Example:
@Tool(name = Some("add"), description = Some("Add two numbers"))
def add(
@ToolParam("First operand") a: Double,
@ToolParam("Second operand") b: Double
@Param("First operand") a: Double,
@Param("Second operand") b: Double
): Double = a + b

@Prompt(name = Some("greet"), description = Some("Generate a greeting message"))
def greet(@PromptParam("Name to greet") name: String): String =
def greet(@Param("Name to greet") name: String): String =
s"Hello, $name!"

@Resource(uri = "file://test", description = Some("Test resource"))
def test(): String = "This is a test"

@Resource(uri = "user://{userId}", description = Some("Test resource"))
def getUser(@ResourceParam("The user id") userId: String): String = s"User ID: $userId"
def getUser(@Param("The user id") userId: String): String = s"User ID: $userId"

object ExampleServer extends ZIOAppDefault:
override def run =
Expand Down
12 changes: 6 additions & 6 deletions scripts/quickstart.sc
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//> using scala 3.7.2
//> using dep com.tjclp::fast-mcp-scala:0.2.0
//> using dep com.tjclp::fast-mcp-scala:0.2.1-SNAPSHOT
//> using options "-Xcheck-macros" "-experimental"

import com.tjclp.fastmcp.core.{Tool, ToolParam, Prompt, PromptParam, Resource, ResourceParam}
import com.tjclp.fastmcp.core.{Tool, Param, Prompt, Resource}
import com.tjclp.fastmcp.server.FastMcpServer
import com.tjclp.fastmcp.macros.RegistrationMacro.*
import zio.*
Expand All @@ -12,19 +12,19 @@ object Example:

@Tool(name = Some("add"), description = Some("Add two numbers"))
def add(
@ToolParam("First operand") a: Double,
@ToolParam("Second operand") b: Double
@Param("First operand") a: Double,
@Param("Second operand") b: Double
): Double = a + b

@Prompt(name = Some("greet"), description = Some("Generate a greeting message"))
def greet(@PromptParam("Name to greet") name: String): String =
def greet(@Param("Name to greet") name: String): String =
s"Hello, $name!"

@Resource(uri = "file://test", description = Some("Test resource"))
def test(): String = "This is a test"

@Resource(uri = "user://{userId}", description = Some("Test resource"))
def getUser(@ResourceParam("The user id") userId: String): String = s"User ID: $userId"
def getUser(@Param("The user id") userId: String): String = s"User ID: $userId"

object ExampleServer extends ZIOAppDefault:

Expand Down
31 changes: 31 additions & 0 deletions src/main/scala/com/tjclp/fastmcp/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ class Tool(
val timeoutMillis: Option[Long] = None
) extends StaticAnnotation

/** Unified annotation for method parameters across Tools, Resources, and Prompts
*
* This annotation can be used with any @Tool, @Resource, or @Prompt annotated method to provide
* descriptions and metadata for parameters.
*
* @param description
* Description of the parameter for documentation
* @param example
* Optional example value for the parameter
* @param required
* Whether the parameter is required (defaults to true)
* @param schema
* Optional JSON schema override for the parameter type
* @since 0.2.1
*/
class Param(
val description: String,
val example: Option[String] = None,
val required: Boolean = true,
val schema: Option[String] = None
) extends StaticAnnotation

/** Annotation for method parameters in Tool methods
*
* @param description
Expand All @@ -50,7 +72,10 @@ class Tool(
* Whether the parameter is required (defaults to true)
* @param schema
* Optional JSON schema override for the parameter type
* @deprecated
* Use [[Param]] instead. Will be removed in 0.3.0.
*/
@deprecated("Use @Param instead", "0.2.1")
class ToolParam(
val description: String,
val example: Option[String] = None,
Expand Down Expand Up @@ -86,7 +111,10 @@ class Resource(
* A human-readable description for the parameter
* @param required
* Whether the parameter is required (defaults to true)
* @deprecated
* Use [[Param]] instead. Will be removed in 0.3.0.
*/
@deprecated("Use @Param instead", "0.2.1")
class ResourceParam(
val description: String,
val required: Boolean = true
Expand All @@ -110,7 +138,10 @@ class Prompt(
* Description of the parameter for prompt documentation
* @param required
* Whether the parameter is required (defaults to true)
* @deprecated
* Use [[Param]] instead. Will be removed in 0.3.0.
*/
@deprecated("Use @Param instead", "0.2.1")
class PromptParam(
val description: String,
val required: Boolean = true
Expand Down
20 changes: 10 additions & 10 deletions src/main/scala/com/tjclp/fastmcp/examples/AnnotatedServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object AnnotatedServer extends ZIOAppDefault:

@Tool(name = Some("description"))
def generateDescription(
@ToolParam("A description to generate") description: Description
@Param("A description to generate") description: Description
): String =
if description.isUpper then description.text.toUpperCase else description.text

Expand All @@ -48,8 +48,8 @@ object AnnotatedServer extends ZIOAppDefault:
// description = Some("Add two numbers together")
)
def add(
@ToolParam("First number") a: Int,
@ToolParam("Second number") b: Int
@Param("First number") a: Int,
@Param("Second number") b: Int
): Int = a + b

/** More complex calculator tool that handles different operations.
Expand All @@ -60,9 +60,9 @@ object AnnotatedServer extends ZIOAppDefault:
tags = List("math", "calculation")
)
def calculate(
@ToolParam("First number") a: Double,
@ToolParam("Second number") b: Double,
@ToolParam(
@Param("First number") a: Double,
@Param("Second number") b: Double,
@Param(
"Operation to perform (add, subtract, multiply, divide)",
required = false
) operation: String = "add"
Expand Down Expand Up @@ -114,7 +114,7 @@ object AnnotatedServer extends ZIOAppDefault:
mimeType = Some("application/json")
)
def userProfileResource(
@ResourceParam("The unique identifier of the user") userId: String
@Param("The unique identifier of the user") userId: String
): String =
// In a real app, fetch user data based on userId
Map(
Expand All @@ -133,9 +133,9 @@ object AnnotatedServer extends ZIOAppDefault:
mimeType = Some("application/json")
)
def getRepositoryIssue(
@ResourceParam("Repository owner") owner: String,
@ResourceParam("Repository name") repo: String,
@ResourceParam("Issue ID") id: String
@Param("Repository owner") owner: String,
@Param("Repository name") repo: String,
@Param("Issue ID") id: String
): String =
Map(
"owner" -> owner,
Expand Down
20 changes: 20 additions & 0 deletions src/main/scala/com/tjclp/fastmcp/macros/MacroUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,26 @@ private[macros] object MacroUtils:
case None => (None, true) // Defaults if no @PromptParam
}

// Generic helper to extract parameter annotations (Param or legacy specific ones)
// First checks for new @Param, then falls back to context-specific annotation
def extractParamAnnotation(using quotes: Quotes)(
sym: quotes.reflect.Symbol,
fallbackAnnotationType: Option[String] = None
): Option[quotes.reflect.Term] =
import quotes.reflect.*

// First check for the new unified @Param annotation
val paramAnnot = extractAnnotation[com.tjclp.fastmcp.core.Param](sym)
if (paramAnnot.isDefined) return paramAnnot

// Fall back to specific annotations based on context
fallbackAnnotationType match {
case Some("Tool") => extractAnnotation[com.tjclp.fastmcp.core.ToolParam](sym)
case Some("Resource") => extractAnnotation[com.tjclp.fastmcp.core.ResourceParam](sym)
case Some("Prompt") => extractAnnotation[com.tjclp.fastmcp.core.PromptParam](sym)
case _ => None
}

// Helper to parse @Param annotation arguments for @Tool methods
// Returns: (description: Option[String], example: Option[String], required: Boolean, schema: Option[String])
def parseToolParam(using quotes: Quotes)(
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/tjclp/fastmcp/macros/PromptProcessor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ private[macros] object PromptProcessor extends AnnotationProcessorBase:
// 2️⃣ name / description with Scaladoc fallback ------------------------------------------
val (finalName, finalDesc) = nameAndDescription(promptAnnot, methodSym)

// 3️⃣ Collect @PromptParam metadata -------------------------------------------------------
// 3️⃣ Collect @Param/@PromptParam metadata -------------------------------------------------------
val argExprs: List[Expr[PromptArgument]] =
methodSym.paramSymss.headOption.getOrElse(Nil).map { pSym =>
val (descOpt, required) = MacroUtils.parsePromptParamArgs(
MacroUtils.extractAnnotation[PromptParam](pSym)
MacroUtils.extractParamAnnotation(pSym, Some("Prompt"))
)
'{ PromptArgument(${ Expr(pSym.name) }, ${ Expr(descOpt) }, ${ Expr(required) }) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ private[macros] object ResourceProcessor extends AnnotationProcessorBase:
if !isTemplate then '{ None }
else
val list = paramSyms.map { pSym =>
// Extract @ResourceParam description / required
// Extract @Param/@ResourceParam description / required
val (descOpt, required) =
MacroUtils.extractAnnotation[ResourceParam](pSym) match
MacroUtils.extractParamAnnotation(pSym, Some("Resource")) match
case Some(annotTerm) =>
var d: Option[String] = None
var req: Boolean = true
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/com/tjclp/fastmcp/macros/ToolProcessor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ private[macros] object ToolProcessor extends AnnotationProcessorBase:
)
}

// Collect @ToolParam descriptions so we can inject them into the schema
// Collect @Param/@ToolParam descriptions so we can inject them into the schema
val paramDescriptions: Map[String, String] =
methodSym.paramSymss.headOption
.getOrElse(Nil)
.flatMap { pSym =>
MacroUtils
.extractAnnotation[ToolParam](pSym)
.extractParamAnnotation(pSym, Some("Tool"))
.flatMap { annotTerm =>
// description is either the first String literal or the named arg "description"
annotTerm match
Expand Down