Skip to content

VUFIND-1809: Add Model Context Protocol (MCP) server support#4939

Draft
maccabeelevine wants to merge 43 commits intovufind-org:dev-12.0from
maccabeelevine:mcp-server
Draft

VUFIND-1809: Add Model Context Protocol (MCP) server support#4939
maccabeelevine wants to merge 43 commits intovufind-org:dev-12.0from
maccabeelevine:mcp-server

Conversation

@maccabeelevine
Copy link
Member

@maccabeelevine maccabeelevine commented Dec 5, 2025

Per VUFIND-1809, from https://modelcontextprotocol.io/docs/getting-started/intro

MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.

This extends VuFind's existing API capabilities to provide an MCP server, i.e. to respond to MCP queries from AI models.

TODO

@maccabeelevine
Copy link
Member Author

This POC doesn't do anything useful yet (check out the amazing addition function), but it is a working MCP server embedded in VuFind. Next step will be to build some real capabilities, i.e. searching and document retrieval.

The implementation adapts the SDK's example of Symfony integration as well as the work @demiankatz is doing on #4672.

There would be some major dependency roadblocks to actually integrating this into VuFind, as I mentioned on the Jira. This POC uses the official PHP SDK, mcp/sdk, which fails to install because it requires "psr/container": "^2.0" while several of our current dependencies are stuck targeting the 1.x releases.

@maccabeelevine
Copy link
Member Author

Note for testing, I've locally modified the vendor file (yes I know) laminas-servicemanager/src/ServiceManager to make the has constructor compatible with psr/container 2.

public function has($name): bool

'vufind_api' => [
'register_controllers' => [
\VuFindApi\Controller\AdminApiController::class,
// \VuFindApi\Controller\McpController::class,
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure whether this actually belongs in VuFindApi, and if so whether it should be discoverable from the main API endpoint, given the protocol is quite different. To consider.

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps @EreMaijala will have an opinion on this.

@maccabeelevine
Copy link
Member Author

You can test the server in a couple of ways:

  • The official MCP Inspector tool
  • Via VS Code's chat panel, after adding the MCP server via MCP: Add server

either way, hitting a URL like http://localhost:[port]/vufind/api/v1/mcp

@maccabeelevine
Copy link
Member Author

This POC doesn't do anything useful yet (check out the amazing addition function), but it is a working MCP server embedded in VuFind. Next step will be to build some real capabilities, i.e. searching and document retrieval.

It now does a basic keyword search, and record retrieval by ID.

@maccabeelevine
Copy link
Member Author

There would be some major dependency roadblocks to actually integrating this into VuFind, as I mentioned on the Jira. This POC uses the official PHP SDK, mcp/sdk, which fails to install because it requires "psr/container": "^2.0" while several of our current dependencies are stuck targeting the 1.x releases.

To get around this, I've temporarily forked php-sdk to allow it to use "psr/container": "^1.1.2 || ^2.0". This at least resolves all of the build errors, while we work on getting rid of all the old laminas dependencies that are stuck using ^1.0.0.

}

// $content = json_decode($this->params()->getController()->getRequest()->getContent(), true);
// $mcpMethod = $content['method'] ?? '';
Copy link
Member Author

@maccabeelevine maccabeelevine Dec 12, 2025

Choose a reason for hiding this comment

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

I have to think through if this is useful. Methods in MCP are not specific enough, i.e. all tools are method "tools/call" and you need a further param "name" to know what's actually being called. But if we go down the route of allowing selective methods/names, then I also have to consider the fact that "tools/list" would list them all. Etc. for resources, prompts, etc. It's probably a good start just to indivisibly allow or disallow MCP. Of course the permissions.ini still lets you narrow down that single permission by IP address, etc.

protected function outputAuthError(string $messageId): Response
{
$error = new Error($messageId, $this->AUTH_ERROR, 'Access denied');
$response = $this->getResponse();
Copy link
Member Author

Choose a reason for hiding this comment

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

Much of this is borrowed from ApiTrait. Not sure if it's worth refactoring that method for the few bits that are useful here.

@maccabeelevine
Copy link
Member Author

To get around this, I've temporarily forked php-sdk to allow it to use "psr/container": "^1.1.2 || ^2.0". This at least resolves all of the build errors, while we work on getting rid of all the old laminas dependencies that are stuck using ^1.0.0.

Forking is no longer necessary, as the upstream php-sdk now supports earlier psr/container.

Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

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

Thanks, @maccabeelevine, I took a first look through this. I'm not yet familiar with the libraries and technologies being used, so I mainly focused on general design and documentation for this initial pass.

'vufind_api' => [
'register_controllers' => [
\VuFindApi\Controller\AdminApiController::class,
// \VuFindApi\Controller\McpController::class,
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps @EreMaijala will have an opinion on this.

@samuli
Copy link
Contributor

samuli commented Jan 18, 2026

Hello, @EreMaijala found my hobby project (MCP server for Finna.fi) and mentioned that you are working on MCP support for VuFind.

My implementation wraps the VuFind API but prunes and renames fields to make them more LLM-friendly. I've also limited the search options and record fields to improve reliability, though these could be made configurable. Although I built this on a different stack (Cloudflare Workers), it might offer some inspiration.

One feature I found useful is a dedicated "help" tool. It provides extra documentation to the model only when requested, saving context space compared to putting everything in the tool description. This could be helpful for web-based clients like ChatGPT/Claude that don't (at least yet) support local agent "skills."

Links for reference:
Tool descriptions
Help tool content

A tip for iterating: I've found it helpful to ask the LLM for feedback on the tool spec after it fails a complex task.

Copy link
Member

@demiankatz demiankatz left a comment

Choose a reason for hiding this comment

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

Just took another look at this and have a few minor suggestions and ideas below. (I also see that I previously reviewed this and forgot that I had, so sorry if any of these are repeats from before).

@maccabeelevine maccabeelevine changed the base branch from dev to dev-12.0 January 29, 2026 16:53
@maccabeelevine
Copy link
Member Author

Hello, @EreMaijala found my hobby project (MCP server for Finna.fi) and mentioned that you are working on MCP support for VuFind.
...

Thanks belatedly @samuli for the suggestions! You obviously have some great experience building and iterating on this, seeing how the models react. I have to spend some time considering it all. I love what you did with the tool descriptions. Re: the help text, I understand the intent to save context window space, but I hate to not take advantage of the input schema (defining fields and enums)... have to consider more. I also appreciate the tip on iterating with LLM feedback -- so far Gemini CLI has been pretty reliably using the tools I've built here so far, but I can see how that (and the context window problem) would be trickier as the number of capabilities grows.

Can I ask about language choice -- writing all the help text etc. in English? Do the models do a lousy job of interpreting Finnish at this point? Do you anticipate that changing?

@samuli
Copy link
Contributor

samuli commented Jan 31, 2026

@maccabeelevine

Re: the help text, I understand the intent to save context window space, but I hate to not take advantage of the input schema (defining fields and enums)... have to consider more.

I see what you mean. The spec should include enough information for the model to operate the tools and ideally the help tool is not needed at all for this purpose. But it could be a handy way to provide the model a way to request more info when needed (about tools or something else).

Can I ask about language choice -- writing all the help text etc. in English? Do the models do a lousy job of interpreting Finnish at this point? Do you anticipate that changing?

In my experience the frontier models understand and interpret Finnish well (the output might not be 100% natural but that's another issue). Smaller models have worse multilingual skills which is easy to notice from unnatural output but they might still understand the query due to cross-lingual generalization. In other projects I have written the spec in English and included important key terms also in Finnish to make sure that the model understands them.

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.

4 participants