Skip to content

Commit 941f99a

Browse files
authored
Edvardwd/mcp (#1800)
feat: MCP server sample app in examples/
1 parent b560f5d commit 941f99a

File tree

12 files changed

+541
-0
lines changed

12 files changed

+541
-0
lines changed

examples/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ it is useful when you want to quickly do ranking experiments without rewriting a
138138

139139
For any questions, please register at the Vespa Slack and discuss in the general channel.
140140

141+
### Job matching app with MCP Server
142+
143+
[![logo](/assets/vespa-logomark-tiny.png) mcp-server-app](mcp-server-app) This simple sample app combines a job matching platform with an integrated MCP server.
144+
141145
----
142146

143147
### Operations

examples/mcp-server-app/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.venv
2+
*.sh
3+
uv.lock

examples/mcp-server-app/README.md

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<picture>
2+
<source media="(prefers-color-scheme: dark)" srcset="https://assets.vespa.ai/logos/Vespa-logo-green-RGB.svg">
3+
<source media="(prefers-color-scheme: light)" srcset="https://assets.vespa.ai/logos/Vespa-logo-dark-RGB.svg">
4+
<img alt="#Vespa" width="200" src="https://assets.vespa.ai/logos/Vespa-logo-dark-RGB.svg" style="margin-bottom: 25px;">
5+
</picture>
6+
7+
8+
# Vespa sample applications - Job matching app with MCP server
9+
This sample application combines a job search and job recommendation application with a built-in Model Context Protocol (MCP) server.
10+
The point of this sample app is to provide a simple framework for you to experiment with and learn about Vespa's MCP capabilities.
11+
12+
## Prerequisites
13+
- [Docker](https://docs.docker.com/get-docker/) or [Podman](https://podman.io/getting-started/installation)
14+
- [Vespa CLI](https://docs.vespa.ai/en/vespa-cli.html)
15+
- An MCP client (e.g. [Claude Desktop](https://claude.ai/download)
16+
- [UV](https://docs.astral.sh/uv/#installation) (If you want to create more data)
17+
18+
## Getting started
19+
1. Clone this repository and navigate to the `mcp-server-app` directory:
20+
<pre data-test="exec">
21+
$ git clone --depth 1 https://github.com/vespa-engine/sample-apps.git
22+
$ cd sample-apps/examples/mcp-server-app
23+
</pre>
24+
25+
2. Start a Vespa container:
26+
- With Docker:
27+
<pre data-test="exec">
28+
$ docker pull vespaengine/vespa
29+
$ docker run --detach --name vespa --hostname vespa-container \
30+
--publish 127.0.0.1:8080:8080 --publish 127.0.0.1:19071:19071 \
31+
vespaengine/vespa
32+
</pre>
33+
34+
- With Podman:
35+
<pre>
36+
$ podman pull vespaengine/vespa
37+
$ podman run --detach --name vespa --hostname vespa-container \
38+
--publish 127.0.0.1:8080:8080 --publish 127.0.0.1:19071:19071 \
39+
vespaengine/vespa
40+
</pre>
41+
42+
3. Deploy the application and feed data:
43+
<pre data-test="exec">
44+
$ vespa config set target local
45+
</pre>
46+
<pre data-test="exec" data-test-assert-contains="Success">
47+
$ vespa deploy app --wait 300
48+
</pre>
49+
<pre data-test="exec">
50+
$ vespa feed ./dataset/*.jsonl --progress 2
51+
</pre>
52+
53+
4. Connect to the MCP server
54+
- Using Claude Desktop:
55+
Add
56+
```json
57+
"Vespa-mcp-server": {
58+
"command": "npx",
59+
"args": [
60+
"mcp-remote",
61+
"http://localhost:8080/mcp/",
62+
"--transport",
63+
"http-first"
64+
]
65+
}
66+
```
67+
to your `claude_desktop_config.json` file under the `mcpServers` section.
68+
69+
## MCP server capabilities
70+
### Tools
71+
- **`executeQuery`**: Build and execute Vespa queries against the Vespa application.
72+
- **`getSchemas`**: Retrieve the schemas of the Vespa application.
73+
- **`searchDocumentation`**: Search the [Vespa documentation](https://docs.vespa.ai/) for relevant information based on a user input.
74+
75+
### Resources
76+
- **`queryExamples`**: Provides query examples to the MCP client for guidance on how to use the `executeQuery` tool.
77+
78+
### Prompts
79+
- **`listTools`**: Prompt to list the tools and their descriptions of the MCP server.
80+
81+
## App exploration
82+
Since the point of the sample app is to become familiar with Vespa's MCP server capabilities, here are some tasks and questions to explore:
83+
- Find a random candidate amongst your documents and try to find the best matching job for this candidate.
84+
- Of the jobs matching this candidate, where would our candidate have the best chances to land a job? Do any of the jobs have other better candidates?
85+
- Based on your skills and interests, do any of the jobs match your profile?
86+
- Ask the LLM other Vespa related questions and have it search the documentation for you.
87+
- Can the application be improved? Maybe the LLM can help you modify the schemas and datasets? Make sure `generate_data.py` actually generates data that matches the schemas if you modify them.
88+
89+
### Generating data
90+
```bash
91+
cd script
92+
uv sync
93+
source .venv/bin/activate
94+
python generate_data.py
95+
```
96+
97+
### Cleanup
98+
<pre data-test="exec">
99+
$ docker rm -f vespa
100+
</pre>
101+
or
102+
<pre>
103+
$ podman rm -f vespa
104+
</pre>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
schema candidate inherits candidate_matching{
2+
document candidate inherits candidate_matching {
3+
field candidate_id type string {
4+
indexing: summary | attribute
5+
}
6+
7+
field name type string {
8+
indexing: summary | index
9+
index: enable-bm25
10+
}
11+
12+
field candidate_summary type string {
13+
indexing: summary | index
14+
index: enable-bm25
15+
}
16+
17+
field experience_years type int {
18+
indexing: summary | attribute
19+
}
20+
21+
field desired_salary type int {
22+
indexing: summary | attribute
23+
}
24+
}
25+
fieldset default {
26+
fields: name, candidate_summary
27+
}
28+
29+
rank-profile candidate_job_match inherits default {
30+
inputs {
31+
query(job_skills) tensor<float>(skill{})
32+
query(job_location) string
33+
query(job_remote_ok) int
34+
}
35+
36+
function skill_overlap() {
37+
# Dot product of skill tensors
38+
# Example:
39+
# Job: {Python:3, AWS:1}
40+
# Candidate: {Java:2, "Node.js:1", "Python:2"}
41+
# skill_overlap = sum(Python: 3*2, AWS: 1*0, Java: 0*2, Node.js: 0*1) = 6
42+
expression: sum(attribute(skills) * query(job_skills))
43+
}
44+
45+
function location_match() {
46+
# 1 point for matching location and 0.5 if both are okay with remote, else 0
47+
expression: if(attribute(location) == query(job_location), 1, if(attribute(remote_ok) && query(job_remote_ok), 0.5, 0))
48+
}
49+
50+
first-phase {
51+
# Feel free to adjust weights
52+
expression: 50 * skill_overlap() + 20 * location_match()
53+
}
54+
}
55+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
schema candidate_matching {
2+
document candidate_matching {
3+
field skills type tensor<float>(skill{}) {
4+
indexing: summary | attribute
5+
}
6+
7+
field remote_ok type int {
8+
# 1 = true, 0 = false
9+
indexing: summary | attribute
10+
attribute: fast-search
11+
}
12+
13+
field location type string {
14+
indexing: summary | attribute
15+
attribute: fast-search
16+
}
17+
}
18+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
schema job inherits candidate_matching{
2+
document job inherits candidate_matching {
3+
field job_id type string {
4+
indexing: summary | attribute
5+
}
6+
7+
field title type string {
8+
indexing: summary | index
9+
index: enable-bm25
10+
}
11+
12+
field company type string {
13+
indexing: summary | attribute
14+
attribute: fast-search
15+
16+
}
17+
18+
field description type string {
19+
indexing: summary | index
20+
index: enable-bm25
21+
}
22+
23+
field salary_range type predicate {
24+
indexing: summary | attribute
25+
index {
26+
arity: 2
27+
lower-bound: 0
28+
upper-bound: 1000000 # Maximum expected salary (make sure it matches the data)
29+
dense-posting-list-threshold: 0.4
30+
}
31+
}
32+
33+
field posted_date type long {
34+
indexing: summary | attribute
35+
}
36+
}
37+
38+
fieldset default {
39+
fields: title, description
40+
}
41+
42+
rank-profile job_candidate_match inherits default {
43+
inputs {
44+
query(candidate_skills) tensor<float>(skill{})
45+
query(candidate_location) string
46+
query(candidate_desired_salary) double
47+
query(candidate_remote_ok) int
48+
}
49+
50+
function skill_overlap() {
51+
# Dot product of skill tensors
52+
# Example:
53+
# Job: {Python:3, AWS:1}
54+
# Candidate: {Java:2, "Node.js:1", "Python:2"}
55+
# skill_overlap = sum(Python: 3*2, AWS: 1*0, Java: 0*2, Node.js: 0*1) = 6
56+
expression: sum(attribute(skills) * query(candidate_skills))
57+
}
58+
59+
function date_freshness() {
60+
# Gives a score closer to 1 if the posted date is more recent
61+
expression: freshness(posted_date).logscale
62+
}
63+
64+
function location_match() {
65+
# 1 point for matching location and 0.5 if both are okay with remote, else 0
66+
expression: if(attribute(location) == query(candidate_location), 1, if(attribute(remote_ok) && query(candidate_remote_ok), 0.5, 0))
67+
}
68+
69+
first-phase {
70+
# Feel free to adjust weights
71+
expression: 50 * skill_overlap() + 20 * location_match() + 10 * date_freshness()
72+
}
73+
}
74+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<services version="1.0" xmlns:deploy="vespa" xmlns:preprocess="properties">
3+
<container id="default" version="1.0">
4+
<handler id="ai.vespa.mcp.McpJdiscHandler" bundle="mcp-server">
5+
<binding>http://*/mcp/*</binding>
6+
</handler>
7+
<document-api/>
8+
<search/>
9+
<nodes>
10+
<node hostalias="node1"/>
11+
</nodes>
12+
</container>
13+
<content id="content" version="1.0">
14+
<min-redundancy>1</min-redundancy>
15+
16+
<documents>
17+
<document type="candidate" mode="index" />
18+
<document type="job" mode="index" />
19+
</documents>
20+
<nodes>
21+
<node hostalias="node1" distribution-key="0"/>
22+
</nodes>
23+
</content>
24+
</services>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{"put": "id:candidate:candidate::C1", "fields": {"candidate_id": "C1", "name": "Ryan Reyes", "candidate_summary": "Tech enthusiast with 6 years transforming ideas into robust applications.", "skills": {"JavaScript": 2, "Python": 2, "React": 3, "SQL": 2, "Node.js": 1, "Docker": 2}, "experience_years": 6, "location": "Seattle", "desired_salary": 126097, "remote_ok": 1}}
2+
{"put": "id:candidate:candidate::C2", "fields": {"candidate_id": "C2", "name": "Jennifer Allen", "candidate_summary": "Detail-oriented engineer with 4+ years in software development.", "skills": {"React": 1, "AWS": 2, "SQL": 1}, "experience_years": 4, "location": "Seattle", "desired_salary": 135411, "remote_ok": 1}}
3+
{"put": "id:candidate:candidate::C3", "fields": {"candidate_id": "C3", "name": "Hailey Glenn", "candidate_summary": "Results-driven software engineer focused on mobile apps and performance optimization.", "skills": {"React": 2, "AWS": 1}, "experience_years": 5, "location": "Austin", "desired_salary": 137425, "remote_ok": 0}}
4+
{"put": "id:candidate:candidate::C4", "fields": {"candidate_id": "C4", "name": "Thomas Davis", "candidate_summary": "Senior developer specializing in enterprise cloud infrastructure solutions.", "skills": {"JavaScript": 2, "React": 3, "Python": 2}, "experience_years": 10, "location": "Seattle", "desired_salary": 132823, "remote_ok": 0}}
5+
{"put": "id:candidate:candidate::C5", "fields": {"candidate_id": "C5", "name": "Kimberly Willis", "candidate_summary": "6 years of experience building data pipelines with modern tech stack.", "skills": {"AWS": 2, "Python": 3, "Git": 2}, "experience_years": 6, "location": "Austin", "desired_salary": 126963, "remote_ok": 0}}
6+
{"put": "id:candidate:candidate::C6", "fields": {"candidate_id": "C6", "name": "Omar Moore", "candidate_summary": "Results-driven software engineer focused on mobile apps and performance optimization.", "skills": {"Python": 2, "SQL": 3, "React": 1, "Git": 3, "JavaScript": 2}, "experience_years": 2, "location": "Seattle", "desired_salary": 111113, "remote_ok": 1}}
7+
{"put": "id:candidate:candidate::C7", "fields": {"candidate_id": "C7", "name": "Jessica Manning", "candidate_summary": "Versatile engineer experienced in full-stack development and React.", "skills": {"JavaScript": 3, "AWS": 2, "Node.js": 1, "Git": 2, "React": 1}, "experience_years": 3, "location": "Austin", "desired_salary": 121770, "remote_ok": 0}}
8+
{"put": "id:candidate:candidate::C8", "fields": {"candidate_id": "C8", "name": "Rachael Barnes", "candidate_summary": "7 years of experience building mobile apps with modern tech stack.", "skills": {"AWS": 1, "Docker": 1, "SQL": 3, "Node.js": 3, "Python": 1}, "experience_years": 7, "location": "New York", "desired_salary": 131907, "remote_ok": 0}}
9+
{"put": "id:candidate:candidate::C9", "fields": {"candidate_id": "C9", "name": "Janice Clark", "candidate_summary": "Tech enthusiast with 5 years transforming ideas into robust applications.", "skills": {"AWS": 3, "Node.js": 1, "SQL": 1}, "experience_years": 5, "location": "New York", "desired_salary": 120368, "remote_ok": 0}}
10+
{"put": "id:candidate:candidate::C10", "fields": {"candidate_id": "C10", "name": "Brian Lewis", "candidate_summary": "Detail-oriented engineer with 1+ years in software development.", "skills": {"SQL": 2, "Git": 2}, "experience_years": 1, "location": "Austin", "desired_salary": 107685, "remote_ok": 1}}
11+
{"put": "id:candidate:candidate::C11", "fields": {"candidate_id": "C11", "name": "Brenda Martinez", "candidate_summary": "Passionate frontend developer specializing in Node.js and best practices.", "skills": {"Python": 2, "AWS": 2, "Node.js": 2}, "experience_years": 5, "location": "Austin", "desired_salary": 101968, "remote_ok": 0}}
12+
{"put": "id:candidate:candidate::C12", "fields": {"candidate_id": "C12", "name": "Annette Tucker", "candidate_summary": "Self-motivated developer with proven track record in Node.js development.", "skills": {"JavaScript": 3, "Node.js": 1}, "experience_years": 1, "location": "New York", "desired_salary": 120482, "remote_ok": 1}}
13+
{"put": "id:candidate:candidate::C13", "fields": {"candidate_id": "C13", "name": "Paul Richard", "candidate_summary": "Collaborative team player with 6 years delivering quality software.", "skills": {"Node.js": 1, "AWS": 1, "Git": 3, "JavaScript": 2, "Python": 1, "Docker": 2}, "experience_years": 6, "location": "Seattle", "desired_salary": 122259, "remote_ok": 1}}
14+
{"put": "id:candidate:candidate::C14", "fields": {"candidate_id": "C14", "name": "Jason Castillo", "candidate_summary": "2 years of experience building data pipelines with modern tech stack.", "skills": {"AWS": 3, "Node.js": 3, "Docker": 2, "Python": 3}, "experience_years": 2, "location": "San Francisco", "desired_salary": 124142, "remote_ok": 1}}
15+
{"put": "id:candidate:candidate::C15", "fields": {"candidate_id": "C15", "name": "Sheryl Brown", "candidate_summary": "Results-driven devops engineer focused on data pipelines and performance optimization.", "skills": {"Python": 1, "AWS": 2, "Node.js": 3, "Docker": 2}, "experience_years": 2, "location": "New York", "desired_salary": 109313, "remote_ok": 1}}
16+
{"put": "id:candidate:candidate::C16", "fields": {"candidate_id": "C16", "name": "Michael Lopez", "candidate_summary": "Forward-thinking developer passionate about clean code and architecture.", "skills": {"React": 2, "Docker": 2, "Python": 3, "AWS": 2, "Node.js": 1}, "experience_years": 10, "location": "Seattle", "desired_salary": 131405, "remote_ok": 0}}
17+
{"put": "id:candidate:candidate::C17", "fields": {"candidate_id": "C17", "name": "Robert Miles", "candidate_summary": "Creative problem solver with expertise in Node.js. 3 years in tech.", "skills": {"React": 3, "Docker": 2, "Node.js": 1, "AWS": 3}, "experience_years": 3, "location": "Seattle", "desired_salary": 132093, "remote_ok": 1}}
18+
{"put": "id:candidate:candidate::C18", "fields": {"candidate_id": "C18", "name": "Vanessa Bailey", "candidate_summary": "Passionate software engineer specializing in Git and best practices.", "skills": {"Python": 1, "AWS": 2, "Git": 3}, "experience_years": 5, "location": "New York", "desired_salary": 120475, "remote_ok": 1}}
19+
{"put": "id:candidate:candidate::C19", "fields": {"candidate_id": "C19", "name": "Dustin Perez", "candidate_summary": "Detail-oriented engineer with 1+ years in software development.", "skills": {"Python": 3, "React": 1}, "experience_years": 1, "location": "Austin", "desired_salary": 107735, "remote_ok": 1}}
20+
{"put": "id:candidate:candidate::C20", "fields": {"candidate_id": "C20", "name": "Brenda Vasquez", "candidate_summary": "Versatile engineer experienced in full-stack development and Docker.", "skills": {"Docker": 1, "Python": 2, "Git": 2, "Node.js": 3, "SQL": 2, "JavaScript": 3}, "experience_years": 10, "location": "New York", "desired_salary": 99814, "remote_ok": 1}}
21+
{"put": "id:candidate:candidate::C21", "fields": {"candidate_id": "C21", "name": "Melissa Soto", "candidate_summary": "4 years of experience building cloud infrastructure with modern tech stack.", "skills": {"Git": 1, "AWS": 2}, "experience_years": 4, "location": "Austin", "desired_salary": 120485, "remote_ok": 0}}
22+
{"put": "id:candidate:candidate::C22", "fields": {"candidate_id": "C22", "name": "Gary Davies", "candidate_summary": "Senior developer specializing in enterprise APIs solutions.", "skills": {"SQL": 3, "AWS": 2, "Node.js": 2, "Git": 3, "JavaScript": 1}, "experience_years": 6, "location": "Remote", "desired_salary": 145928, "remote_ok": 1}}
23+
{"put": "id:candidate:candidate::C23", "fields": {"candidate_id": "C23", "name": "Matthew Stevens", "candidate_summary": "Versatile engineer experienced in full-stack development and AWS.", "skills": {"Node.js": 3, "Docker": 3, "SQL": 1, "JavaScript": 2, "AWS": 1}, "experience_years": 7, "location": "Austin", "desired_salary": 114453, "remote_ok": 1}}
24+
{"put": "id:candidate:candidate::C24", "fields": {"candidate_id": "C24", "name": "Ms. Maria Hall PhD", "candidate_summary": "Detail-oriented engineer with 4+ years in software development.", "skills": {"JavaScript": 1, "SQL": 2, "Node.js": 1, "Python": 2, "AWS": 2}, "experience_years": 4, "location": "Remote", "desired_salary": 130316, "remote_ok": 1}}
25+
{"put": "id:candidate:candidate::C25", "fields": {"candidate_id": "C25", "name": "Monique Kirby", "candidate_summary": "Passionate frontend developer specializing in Git and best practices.", "skills": {"Node.js": 3, "Docker": 3, "JavaScript": 3, "SQL": 3, "AWS": 3, "Git": 3}, "experience_years": 2, "location": "San Francisco", "desired_salary": 146162, "remote_ok": 1}}
26+
{"put": "id:candidate:candidate::C26", "fields": {"candidate_id": "C26", "name": "Kyle Morris", "candidate_summary": "Senior developer specializing in enterprise cloud infrastructure solutions.", "skills": {"Python": 2, "React": 2, "SQL": 1, "AWS": 1, "JavaScript": 2, "Git": 3}, "experience_years": 6, "location": "Austin", "desired_salary": 97615, "remote_ok": 0}}
27+
{"put": "id:candidate:candidate::C27", "fields": {"candidate_id": "C27", "name": "David Duffy", "candidate_summary": "Collaborative team player with 4 years delivering quality software.", "skills": {"Python": 3, "SQL": 1, "React": 2, "Node.js": 1, "JavaScript": 2, "Docker": 3}, "experience_years": 4, "location": "New York", "desired_salary": 116292, "remote_ok": 1}}
28+
{"put": "id:candidate:candidate::C28", "fields": {"candidate_id": "C28", "name": "Catherine Grimes", "candidate_summary": "Goal-oriented devops engineer with strong analytical and problem-solving skills.", "skills": {"AWS": 1, "React": 1}, "experience_years": 8, "location": "Seattle", "desired_salary": 117478, "remote_ok": 1}}
29+
{"put": "id:candidate:candidate::C29", "fields": {"candidate_id": "C29", "name": "Mrs. Kaitlyn Dennis", "candidate_summary": "Forward-thinking developer passionate about clean code and architecture.", "skills": {"React": 3, "Git": 3, "JavaScript": 3, "Docker": 2, "SQL": 3, "Python": 3}, "experience_years": 6, "location": "Remote", "desired_salary": 95766, "remote_ok": 1}}
30+
{"put": "id:candidate:candidate::C30", "fields": {"candidate_id": "C30", "name": "Heather Castaneda", "candidate_summary": "Results-driven data scientist focused on APIs and performance optimization.", "skills": {"Node.js": 3, "SQL": 3, "Git": 2}, "experience_years": 9, "location": "Remote", "desired_salary": 106934, "remote_ok": 1}}

0 commit comments

Comments
 (0)