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
54 changes: 53 additions & 1 deletion core/src/org/labkey/core/CoreMcp.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.labkey.api.study.Study;
import org.labkey.api.study.StudyService;
import org.labkey.api.util.HtmlString;
import org.labkey.api.view.ActionURL;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.mcp.annotation.McpResource;
import org.springframework.ai.tool.annotation.Tool;
Expand Down Expand Up @@ -92,14 +93,28 @@ String listContainers(ToolContext toolContext)
@Tool(description = "Every tool in this MCP requires a container path, e.g. MyProject/MyFolder. A container is also called a folder or project. " +
"Please prompt the user for a container path and use this tool to save the path for this MCP session. The user can also change the container " +
"during the session using this tool. The user must have read permissions in the container, in other words, the path must be on the list that " +
"the listContainers tool returns. Don't suggest a leading slash on the path because typing a slash in some LLM clients triggers custom shortcuts.")
"the listContainers tool returns. Don't suggest a leading slash on the path because typing a slash in some LLM clients triggers custom shortcuts. " +
"Alternately, the user may provide a LabKey server URL like http://localhost:8080/StudyVerifyProject/My%20Study/project-begin.view. " +
"The container path is encoded in the URL and can be accepted as a valid parameter.")
@RequiresNoPermission // Because we don't have a container yet, but the tool will verify read permission before setting the container
String setContainer(ToolContext context, @ToolParam(description = "Container path, e.g. MyProject/MyFolder") String containerPath)
{
final String message;

Container container = ContainerManager.getForPath(containerPath);

if (null == container)
{
try
{
var url = new ActionURL(containerPath);
container = ContainerManager.getForURL(url);
}
catch (IllegalArgumentException x)
{
}
}

// Must exist and user must have read permission to set a container. Note: Send the same message in either
// case to prevent information exposure.
if (container == null || !container.hasPermission(getUser(context), ReadPermission.class))
Expand Down Expand Up @@ -132,4 +147,41 @@ public ReadResourceResult getFileBasedModuleDevelopmentGuide() throws IOExceptio
)
));
}

@McpResource(
uri = "resource://org/labkey/core/DataAnalysis_Python.md",
mimeType = "application/markdown",
name = "Python Data Analysis Development Guide",
description = "Provide documentation for developers using Python to analyze LabKey data")
public ReadResourceResult getPythonDataAnalysisGuide() throws IOException
{
incrementResourceRequestCount("Python Data Analysis");
String markdown = IOUtils.resourceToString("org/labkey/core/DataAnalysis_Python.md", null, CoreModule.class.getClassLoader());
return new ReadResourceResult(List.of(
new McpSchema.TextResourceContents(
"resource://org/labkey/core/DataAnalysis_Python.md",
"application/markdown",
markdown
)
));
}

@McpResource(
uri = "resource://org/labkey/core/DataAnalysis_R.md",
mimeType = "application/markdown",
name = "R Data Analysis Development Guide",
description = "Provide documentation for developers using R to analyze LabKey data")
public ReadResourceResult getRDataAnalysisGuide() throws IOException
{
incrementResourceRequestCount("R Data Analysis");
String markdown = IOUtils.resourceToString("org/labkey/core/DataAnalysis_R.md", null, CoreModule.class.getClassLoader());
return new ReadResourceResult(List.of(
new McpSchema.TextResourceContents(
"resource://org/labkey/core/DataAnalysis_R.md",
"application/markdown",
markdown
)
));
}

}
Loading
Loading