Mockgun
Mockgun is a testing utility that simulates a ShotGrid instance in memory. ShotGrid MCP Server includes an enhanced version of Mockgun that provides additional functionality and better compatibility with the ShotGrid API.
Why Use Mockgun?
Mockgun provides several benefits for development and testing:
- No ShotGrid Instance Required: Develop and test without connecting to a real ShotGrid instance.
- Faster Testing: In-memory operations are much faster than API calls.
- Controlled Environment: Create a consistent, predictable testing environment.
- No Side Effects: Test operations that would modify data without affecting real data.
- Offline Development: Develop and test without an internet connection.
Using Mockgun
Basic Setup
To use Mockgun, set use_mockgun=True
when creating your server:
from shotgrid_mcp_server import ShotGridMCPServer
# Create a server with Mockgun
server = ShotGridMCPServer(
name="ShotGrid Test Server",
use_mockgun=True
)
# Now server.connection is a Mockgun instance
With Schema Files
For more realistic testing, provide schema files:
server = ShotGridMCPServer(
name="ShotGrid Test Server",
use_mockgun=True,
schema_path="tests/data/schema.bin",
entity_schema_path="tests/data/entity_schema.bin"
)
Creating Test Data
You can create test data in Mockgun just like you would with the real ShotGrid API:
# Create a test project
project = server.connection.create("Project", {
"name": "Test Project",
"code": "TEST",
"sg_status": "Active"
})
# Create a test sequence
sequence = server.connection.create("Sequence", {
"code": "SEQ001",
"project": {"type": "Project", "id": project["id"]}
})
# Create test shots
for i in range(1, 6):
server.connection.create("Shot", {
"code": f"SH{i:03d}",
"sg_status_list": "ip",
"project": {"type": "Project", "id": project["id"]},
"sg_sequence": {"type": "Sequence", "id": sequence["id"]}
})
Using Startup Handlers
A convenient way to create test data is with startup handlers:
from shotgrid_mcp_server import ShotGridMCPServer
server = ShotGridMCPServer(
name="ShotGrid Test Server",
use_mockgun=True
)
@server.on_startup
def create_test_data():
"""Create test data when the server starts."""
# Create a test project
project = server.connection.create("Project", {
"name": "Test Project",
"code": "TEST",
"sg_status": "Active"
})
# Create test users
alice = server.connection.create("HumanUser", {
"name": "Alice",
"login": "alice",
"email": "alice@example.com"
})
bob = server.connection.create("HumanUser", {
"name": "Bob",
"login": "bob",
"email": "bob@example.com"
})
# Create test tasks
server.connection.create("Task", {
"content": "Model Character",
"sg_status_list": "ip",
"project": {"type": "Project", "id": project["id"]},
"task_assignees": [{"type": "HumanUser", "id": alice["id"]}]
})
server.connection.create("Task", {
"content": "Rig Character",
"sg_status_list": "rdy",
"project": {"type": "Project", "id": project["id"]},
"task_assignees": [{"type": "HumanUser", "id": bob["id"]}]
})
@server.tool()
def find_tasks(assigned_to: str = None):
"""Find tasks, optionally filtered by assignee."""
filters = []
if assigned_to:
filters.append(["task_assignees.HumanUser.name", "is", assigned_to])
return server.connection.find(
"Task",
filters,
["content", "sg_status_list", "task_assignees"]
)
Enhanced Mockgun Features
ShotGrid MCP Server’s Mockgun implementation includes several enhancements over the standard Mockgun:
Field Hopping Support
Mockgun supports “field hopping” (dot notation) in filters:
# Find shots in a specific sequence
shots = server.connection.find(
"Shot",
[["sg_sequence.Sequence.code", "is", "SEQ001"]],
["code"]
)
# Find tasks assigned to a specific user
tasks = server.connection.find(
"Task",
[["task_assignees.HumanUser.name", "contains", "Alice"]],
["content"]
)
Advanced Filters
Mockgun supports a wide range of filter operators:
# Find shots with codes starting with "SH"
shots = server.connection.find(
"Shot",
[["code", "starts_with", "SH"]],
["code"]
)
# Find assets created in the last 7 days
import datetime
seven_days_ago = datetime.datetime.now() - datetime.timedelta(days=7)
assets = server.connection.find(
"Asset",
[["created_at", "greater_than", seven_days_ago]],
["code"]
)
# Find tasks with multiple conditions
tasks = server.connection.find(
"Task",
[
["sg_status_list", "in", ["ip", "rdy"]],
["content", "contains", "Character"]
],
["content", "sg_status_list"]
)
Batch Operations
Mockgun supports batch operations for creating, updating, and deleting entities:
# Batch create multiple entities
batch_data = [
{
"request_type": "create",
"entity_type": "Shot",
"data": {
"code": "SH010",
"project": {"type": "Project", "id": 1}
}
},
{
"request_type": "create",
"entity_type": "Shot",
"data": {
"code": "SH011",
"project": {"type": "Project", "id": 1}
}
}
]
results = server.connection.batch(batch_data)
# Batch update and delete
batch_data = [
{
"request_type": "update",
"entity_type": "Shot",
"entity_id": 1,
"data": {
"sg_status_list": "cmpt"
}
},
{
"request_type": "delete",
"entity_type": "Shot",
"entity_id": 2
}
]
results = server.connection.batch(batch_data)
Schema Validation
Mockgun validates entity types and fields against the schema:
try:
# This will fail if "NonExistentEntity" is not in the schema
server.connection.create("NonExistentEntity", {"name": "Test"})
except ValueError as e:
print(f"Error: {e}")
try:
# This will fail if "non_existent_field" is not in the schema for Project
server.connection.create("Project", {"non_existent_field": "Test"})
except ValueError as e:
print(f"Error: {e}")
Testing with Mockgun
Unit Testing
Mockgun is particularly useful for unit testing:
import unittest
from shotgrid_mcp_server import ShotGridMCPServer
class TestShotFunctions(unittest.TestCase):
def setUp(self):
# Create a server with Mockgun for each test
self.server = ShotGridMCPServer(
name="Test Server",
use_mockgun=True
)
# Create test data
self.project = self.server.connection.create("Project", {
"name": "Test Project",
"code": "TEST"
})
self.sequence = self.server.connection.create("Sequence", {
"code": "SEQ001",
"project": {"type": "Project", "id": self.project["id"]}
})
for i in range(1, 6):
self.server.connection.create("Shot", {
"code": f"SH{i:03d}",
"sg_status_list": "ip" if i < 3 else "cmpt",
"project": {"type": "Project", "id": self.project["id"]},
"sg_sequence": {"type": "Sequence", "id": self.sequence["id"]}
})
def test_find_shots(self):
# Test the find_shots function
@self.server.tool()
def find_shots(status=None):
filters = []
if status:
filters.append(["sg_status_list", "is", status])
return self.server.connection.find("Shot", filters, ["code"])
# Test without status filter
all_shots = find_shots()
self.assertEqual(len(all_shots), 5)
# Test with status filter
ip_shots = find_shots(status="ip")
self.assertEqual(len(ip_shots), 2)
cmpt_shots = find_shots(status="cmpt")
self.assertEqual(len(cmpt_shots), 3)
Integration Testing
For integration testing with MCP clients:
import asyncio
from shotgrid_mcp_server import ShotGridMCPServer
from mcp.client import Client
async def test_mcp_integration():
# Create and start a server
server = ShotGridMCPServer(
name="Test Server",
use_mockgun=True
)
# Create test data
@server.on_startup
def create_test_data():
server.connection.create("Project", {
"name": "Test Project",
"code": "TEST"
})
# Define a tool
@server.tool()
def find_projects():
return server.connection.find("Project", [], ["name", "code"])
# Start the server
await server.start(host="localhost", port=8765)
try:
# Connect with an MCP client
client = Client("http://localhost:8765")
# List available tools
tools = await client.list_tools()
print(f"Available tools: {[tool.name for tool in tools]}")
# Call the find_projects tool
result = await client.call_tool("find_projects", {})
print(f"Projects: {result}")
# Verify the result
assert len(result) == 1
assert result[0]["name"] == "Test Project"
assert result[0]["code"] == "TEST"
print("Integration test passed!")
finally:
# Stop the server
await server.stop()
if __name__ == "__main__":
asyncio.run(test_mcp_integration())
Best Practices
-
Use Real Schema: For the most accurate testing, use schema files exported from your production ShotGrid instance.
-
Create Realistic Test Data: Set up test data that closely resembles your production data.
-
Test Edge Cases: Use Mockgun to test error conditions and edge cases that would be difficult to test with a real ShotGrid instance.
-
Reset Between Tests: Create a fresh Mockgun instance for each test to ensure a clean state.
-
Validate Against Real ShotGrid: Periodically validate your tests against a real ShotGrid instance to ensure Mockgun’s behavior matches.
Next Steps
Now that you understand Mockgun, you can: