Skip to content

feat: minimal mcp server#316

Open
timurbazhirov wants to merge 4 commits into
mainfrom
exp/mcp-server
Open

feat: minimal mcp server#316
timurbazhirov wants to merge 4 commits into
mainfrom
exp/mcp-server

Conversation

@timurbazhirov
Copy link
Copy Markdown
Member

@timurbazhirov timurbazhirov commented May 17, 2026

Summary by CodeRabbit

  • New Features
    • Added MCP server support for Mat3ra operations, enabling integration with Claude Desktop and other MCP-compatible clients.
    • Exposed tools for materials, workflows, and job management through the MCP interface.
    • Included setup documentation and configuration templates.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 17, 2026

Warning

Rate limit exceeded

@timurbazhirov has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 35 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 158ac660-3c32-4977-a812-46def33cc364

📥 Commits

Reviewing files that changed from the base of the PR and between 8019739 and 405a0ee.

📒 Files selected for processing (7)
  • mcp_server/.env.example
  • mcp_server/AGENTS.md
  • mcp_server/README.md
  • mcp_server/code_server.py
  • mcp_server/intent.py
  • mcp_server/pyproject.toml
  • mcp_server/server.py
📝 Walkthrough

Walkthrough

This PR introduces a complete MCP (Model Context Protocol) server that enables Claude Desktop to invoke Mat3ra platform operations. It includes configuration templates, Python packaging setup, core server implementation with tool registration and dispatch logic, and comprehensive documentation for deployment and usage.

Changes

Mat3ra MCP Server

Layer / File(s) Summary
Documentation, Configuration, and Setup
mcp_server/README.md, mcp_server/.env.example, mcp_server/pyproject.toml
README documents the available tools (materials, workflows, jobs operations) and local setup steps; .env.example provides credential and API endpoint placeholders; pyproject.toml declares dependencies (mcp>=1.0, mat3ra-api-client) and registers the mat3ra-mcp CLI entry point.
Server Implementation
mcp_server/server.py (docstring, config, helpers, tools, dispatch)
Server module loads credentials and API configuration from environment variables; JSON helper formats structured responses; MCP server instance defines a tool catalog with names, descriptions, and input schemas for seven operations; call_tool and _dispatch route invocations by name, construct owner-scoped Mat3ra API calls, and return formatted results or errors.
Server Startup and Entry Point
mcp_server/server.py (main)
Entry point initializes the MCP stdio server using SDK InitializationOptions and stdio_server, registers the request handlers, and runs the async event loop for bidirectional client communication.

Sequence Diagram

sequenceDiagram
  participant Client as MCP Client
  participant Server as MCP Server
  participant Dispatch as _dispatch Router
  participant Mat3raAPI as Mat3ra API Client
  Client->>Server: call_tool(tool_name, arguments)
  Server->>Dispatch: route by tool_name
  Dispatch->>Mat3raAPI: invoke endpoint method (list/get/create)
  Mat3raAPI-->>Dispatch: return result or raise exception
  Dispatch-->>Server: structured response or error
  Server-->>Client: TextContent(formatted result)
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A server springs forth, with tools so fine,
Materials, workflows, jobs aligned!
Claude now whispers to Mat3ra's keep,
Through MCP's bridge, both swift and deep.
~🍃 A rabbit's refactor, bringing worlds together!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: minimal mcp server' accurately summarizes the main change: introducing a minimal MCP (Model Context Protocol) server implementation with documentation, configuration files, and core server logic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exp/mcp-server

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mcp_server/pyproject.toml`:
- Around line 11-13: The console script currently points to the async function
main(), which will be invoked without awaiting; add a synchronous wrapper
function (e.g., define def cli(): import asyncio and call asyncio.run(main()))
in mcp_server.server and update the project script entry so mat3ra-mcp points to
mcp_server.server:cli instead of mcp_server.server:main; ensure the new cli
wraps the existing async def main() and is exported for the entry point.

In `@mcp_server/README.md`:
- Around line 31-32: The README currently instructs users to set ACCOUNT_ID and
AUTH_TOKEN but the server expects MAT3RA_ACCOUNT_ID and MAT3RA_AUTH_TOKEN;
update the docs to use the exact environment variable names MAT3RA_ACCOUNT_ID
and MAT3RA_AUTH_TOKEN (and mention where to obtain their values) so they match
the code that reads MAT3RA_ACCOUNT_ID / MAT3RA_AUTH_TOKEN.
- Line 35: Replace the brittle shell example "export $(cat mcp_server/.env |
grep -v '^#' | xargs)" with a safe, whitespace-preserving pattern: enable
automatic export, source the file, then disable automatic export (e.g. use "set
-a; source mcp_server/.env; set +a"), or alternatively show a robust while-read
loop that ignores comments and preserves spacing; update the README to use one
of these safer approaches and ensure the exact original command string is
removed.

In `@mcp_server/server.py`:
- Around line 54-57: Remove the duplicate/unused mcp.server.stdio import: keep a
single import of mcp.server.stdio at the top-level and delete the redundant
import inside main(); ensure you still import Server and InitializationOptions
from mcp.server and mcp.server.models respectively, and update any references to
stdio to use the single retained import so lint F401/F811 are resolved.
- Around line 263-267: The handler currently uses q.setdefault("owner._id",
owner) which allows a client-supplied owner._id to persist; replace those
setdefault calls with an unconditional assignment q["owner._id"] = owner to
enforce server-side owner scoping (locate the q dict created from
args.get("query", {}), the q.setdefault("owner._id", owner) occurrences and
change them to q["owner._id"] = owner). Apply the same change for the other
similar occurrences mentioned so that any client-provided owner._id is
overridden by the authenticated owner before creating MaterialEndpoints or
executing the query.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6aa7ad52-a8f0-41ef-b46b-a5bbbf18d018

📥 Commits

Reviewing files that changed from the base of the PR and between 38c1137 and 8019739.

📒 Files selected for processing (4)
  • mcp_server/.env.example
  • mcp_server/README.md
  • mcp_server/pyproject.toml
  • mcp_server/server.py

Comment thread mcp_server/pyproject.toml
Comment on lines +11 to +13
[project.scripts]
mat3ra-mcp = "mcp_server.server:main"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify entrypoint target and callable type in repo sources
rg -n '^\[project\.scripts\]|mat3ra-mcp\s*=|^async def main|^def cli' mcp_server/pyproject.toml mcp_server/server.py

Repository: Exabyte-io/api-examples

Length of output: 224


🏁 Script executed:

#!/bin/bash
# Search for any cli wrapper function and check full context around main
rg -A 5 -B 2 'def cli|def main' mcp_server/server.py

Repository: Exabyte-io/api-examples

Length of output: 336


Console script points to an async function and won't run correctly.

mat3ra-mcp = "mcp_server.server:main" targets async def main() (line 313), so the entry point will call it without awaiting. Expose a sync wrapper (e.g., def cli(): asyncio.run(main())) and point the script to that wrapper.

Suggested change
# mcp_server/server.py
+def cli() -> None:
+    import asyncio
+    asyncio.run(main())

# mcp_server/pyproject.toml
 [project.scripts]
-mat3ra-mcp = "mcp_server.server:main"
+mat3ra-mcp = "mcp_server.server:cli"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_server/pyproject.toml` around lines 11 - 13, The console script currently
points to the async function main(), which will be invoked without awaiting; add
a synchronous wrapper function (e.g., define def cli(): import asyncio and call
asyncio.run(main())) in mcp_server.server and update the project script entry so
mat3ra-mcp points to mcp_server.server:cli instead of mcp_server.server:main;
ensure the new cli wraps the existing async def main() and is exported for the
entry point.

Comment thread mcp_server/README.md Outdated
Comment thread mcp_server/README.md Outdated
Comment thread mcp_server/server.py
Comment thread mcp_server/server.py
Comment on lines +263 to +267
q: dict = args.get("query", {})
if formula := args.get("formula"):
q["formula"] = formula
q.setdefault("owner._id", owner)
endpoint = MaterialEndpoints(*ep)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce owner scoping instead of allowing query override.

Using setdefault("owner._id", owner) lets client input keep a custom owner._id value. Force this field to the authenticated owner to prevent cross-owner query attempts.

Suggested change
     if name == "list_materials":
         q: dict = args.get("query", {})
         if formula := args.get("formula"):
             q["formula"] = formula
-        q.setdefault("owner._id", owner)
+        q["owner._id"] = owner
         endpoint = MaterialEndpoints(*ep)
         return endpoint.list(q)
@@
     if name == "list_workflows":
         q = args.get("query", {})
-        q.setdefault("owner._id", owner)
+        q["owner._id"] = owner
         endpoint = WorkflowEndpoints(*ep)
         return endpoint.list(q)
@@
     if name == "list_jobs":
         q = args.get("query", {})
-        q.setdefault("owner._id", owner)
+        q["owner._id"] = owner
         endpoint = JobEndpoints(*ep)
         return endpoint.list(q)

Also applies to: 279-280, 285-286

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mcp_server/server.py` around lines 263 - 267, The handler currently uses
q.setdefault("owner._id", owner) which allows a client-supplied owner._id to
persist; replace those setdefault calls with an unconditional assignment
q["owner._id"] = owner to enforce server-side owner scoping (locate the q dict
created from args.get("query", {}), the q.setdefault("owner._id", owner)
occurrences and change them to q["owner._id"] = owner). Apply the same change
for the other similar occurrences mentioned so that any client-provided
owner._id is overridden by the authenticated owner before creating
MaterialEndpoints or executing the query.

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.

1 participant