DevHub CMS MCP
Official
by devhub
- devhub-cms-mcp
- src
- devhub_cms_mcp
import json
import os
from mcp.server.fastmcp import FastMCP
from requests_oauthlib import OAuth1Session
# Initialize FastMCP server
mcp = FastMCP(
"DevHub CMS MCP",
description="Integration with DevHub CMS to manage content")
def get_client():
"""Get DevHub API client and base_url."""
client = OAuth1Session(
os.environ['DEVHUB_API_KEY'],
client_secret=os.environ['DEVHUB_API_SECRET'])
base_url = '{}/api/v2/'.format(os.environ['DEVHUB_BASE_URL'])
return client, base_url
@mcp.tool()
def get_hours_of_operation(location_id: int, hours_type: str = 'primary') -> list:
"""Get the hours of operation for a DevHub location
Returns a list of items representing days of the week
Except for the special case formatting, this object is a list of 7 items which represent each day.
Each day can can have one-four time ranges. For example, two time ranges denotes a "lunch-break". No time ranges denotes closed.
Examples:
9am-5pm [["09:00:00", "17:00:00"]]
9am-12pm and 1pm-5pm [["09:00:00", "12:00:00"], ["13:00:00", "17:00:00"]]
Closed - an empty list []
Args:
location_id: DevHub Location ID
hours_type: Defaults to 'primary' unless the user specifies a different type
"""
client, base_url = get_client()
r = client.get('{}locations/{}'.format(base_url, location_id))
content = json.loads(r.content)
return content['hours_by_type'].get(hours_type, [])
@mcp.tool()
def update_hours(location_id: int, new_hours: list, hours_type: str = 'primary') -> str:
"""Update the hours of operation for a DevHub location
Send a list of items representing days of the week
Except for the special case formatting, this object is a list of 7 items which represent each day.
Each day can can have one-four time ranges. For example, two time ranges denotes a "lunch-break". No time ranges denotes closed.
Examples:
9am-5pm [["09:00:00", "17:00:00"]]
9am-12pm and 1pm-5pm [["09:00:00", "12:00:00"], ["13:00:00", "17:00:00"]]
Closed - an empty list []
Args:
location_id: DevHub Location ID
new_hours: Structured format of the new hours
hours_type: Defaults to 'primary' unless the user specifies a different type
"""
client, base_url = get_client()
r = client.put(
'{}locations/{}/'.format(base_url, location_id),
json={
'hours': [
{
'type': hours_type,
'hours': new_hours,
}
]
},
)
content = json.loads(r.content)
return 'Updated successfully'
@mcp.tool()
def upload_image(base64_image_content: str, filename: str) -> str:
"""Upload an image to the DevHub media gallery
Supports webp, jpeg and png images
Args:
base64_image_content: Base 64 encoded content of the image file
filename: Filename including the extension
"""
client, base_url = get_client()
payload = {
'type': 'image',
'upload': {
'file': base64_image_content,
'filename': filename,
}
}
r = client.post(
'{}images/'.format(base_url),
json=payload,
)
image = r.json()
return f"""
Image ID: {image['id']}
Image Path (for use in HTML src attributes): {image['absolute_path']}
"""
@mcp.tool()
def get_blog_post(post_id: int) -> str:
"""Get a single blog post
Args:
post_id: Blog post id
"""
client, base_url = get_client()
r = client.get('{}posts/{}/'.format(base_url, post_id))
post = r.json()
return f"""
Post ID: {post['id']}
Title: {post['title']}
Date: {post['date']}
Content (HTML):
{post['content']}
"""
@mcp.tool()
def create_blog_post(site_id: int, title: str, content: str) -> str:
"""Create a new blog post
Args:
site_id: Website ID where the post will be published. Prompt the user for this ID.
title: Blog post title
content: HTML content of blog post. Should not include a <h1> tag, only h2+
"""
client, base_url = get_client()
payload = {
'content': content,
'site_id': site_id,
'title': title,
}
r = client.post(
'{}posts/'.format(base_url),
json=payload,
)
post = r.json()
return f"""
Post ID: {post['id']}
Title: {post['title']}
Date: {post['date']}
Content (HTML):
{post['content']}
"""
@mcp.tool()
def update_blog_post(post_id: int, title: str = None, content: str = None) -> str:
"""Update a single blog post
Args:
post_id: Blog post ID
title: Blog post title
content: HTML content of blog post. Should not include a <h1> tag, only h2+
"""
client, base_url = get_client()
payload = {}
if content:
payload['content'] = content
if title:
payload['title'] = title
r = client.put(
'{}posts/{}/'.format(base_url, post_id),
json=payload,
)
post = r.json()
return f"""
Post ID: {post['id']}
Title: {post['title']}
Date: {post['date']}
Content (HTML):
{post['content']}
"""
@mcp.tool()
def get_nearest_location(business_id: int, latitude: float, longitude: float) -> str:
"""Get the nearest DevHub location
Args:
business_id: DevHub Business ID associated with the location. Prompt the user for this ID
latitude: Latitude of the location
longitude: Longitude of the location
"""
client, base_url = get_client()
r = client.get('{}locations/'.format(base_url), params={
'business_id': business_id,
'near_lat': latitude,
'near_lon': longitude,
})
objects = json.loads(r.content)['objects']
if objects:
return f"""
Location ID: {objects[0]['id']}
Location name: {objects[0]['location_name']}
Location url: {objects[0]['location_url']}
Street: {objects[0]['street']}
City: {objects[0]['city']}
State: {objects[0]['state']}
Country: {objects[0]['country']}
"""
def main():
"""Run the MCP server"""
mcp.run(transport='stdio')
if __name__ == "__main__":
main()