How Openship MCP Endpoint Exposes API Routes to AI Agents with Permission Checking

The Openship MCP endpoint at /api/mcp exposes API routes as stateless JSON-RPC 2.0 tools, enforcing identical permission checks as standard HTTP requests by forwarding bearer tokens through the full Hono middleware stack.

Openship is an open-source logistics and project management platform available in the oblien/openship repository. The Openship MCP endpoint exposes API routes to AI agents with permission checking by transforming eligible HTTP routes into discoverable JSON-RPC tools while routing every invocation through the application's native authentication and authorization layers, ensuring no security logic is duplicated.

Authentication and Principal Resolution

Before an AI agent can discover or invoke tools, the MCP layer establishes identity through the same bearer token authentication used by standard API clients.

Token Parsing and Identity Extraction

In apps/api/src/modules/mcp/mcp.routes.ts (lines 27-57), the parseBearerToken function extracts the Bearer token from the Authorization header. This token—whether a Personal Access Token (PAT) or OAuth token—is mapped to a principal object containing the user's role, read-only status, granted resource-type set, and the special "own-projects" flag. This principal resolution mirrors the standard authMiddleware flow, ensuring that MCP requests use the exact same identity model as regular HTTP API calls according to the oblien/openship source code.

Tool Discovery and Capability Filtering

Once authenticated, the system determines which API routes the AI agent may invoke by inspecting the route registry and filtering against the principal's permissions.

Route Registry Inspection and MCP Opt-In

The getRouteRegistry function scans the application's HTTP routes using helpers from apps/api/src/lib/route-permission.ts, including isPublicSpec and parsePermissionTag, to identify candidates for MCP exposure. A route becomes available as a tool only if its OpenAPI specification contains an mcp block, making exposure opt-in rather than default. The getMcpTools() function in apps/api/src/modules/mcp/mcp-tools.ts (lines 14-78) constructs a McpToolDef for each eligible route, extracting path parameters, body schemas, and permission metadata.

Principal-Based Tool Filtering

After generating the complete tool list, filterToolsForPrincipal() applies capability-based filtering using the principal's role, read-only status, granted root types, and the own-projects create-grant. This ensures the AI agent receives a capability-aware catalogue containing only the tools it is explicitly authorized to invoke. The filtering logic references roleAllowsResourceType and other permission checks from apps/api/src/lib/permission.ts.

Tool Execution with Full Permission Enforcement

When an AI agent invokes a tool, the MCP layer executes the underlying HTTP request while preserving all security boundaries.

Internal HTTP Request Dispatch

The dispatchTool() function in apps/api/src/modules/mcp/mcp-dispatch.ts (lines 21-70) handles tool execution by creating an internal HTTP request that re-uses the real Hono application instance (app.fetch). The original bearer token is forwarded via the authorization header, along with optional x-organization-id and relevant body or query parameters. Because this request traverses the full middleware stack, including authMiddleware and the permission logic in permission.ts, every tool call undergoes the exact same validation as external API requests.

JSON-RPC Response Wrapping

The internal request's result (or error) is wrapped into a JSON-RPC 2.0 response format. If the bearer token lacks permission for the requested operation, the internal request fails and returns a JSON-RPC response with isError set to true, preserving the HTTP error code and message within the response payload.

JSON-RPC Method Examples

The MCP endpoint accepts standard JSON-RPC 2.0 requests. Below are examples of the three primary interactions: initialization, tool discovery, and tool invocation.

Initialize an MCP Session

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": { "protocolVersion": "2025-06-18" }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false }, "prompts": { "listChanged": false } },
    "serverInfo": { "name": "openship", "version": "1.0.0" }
  }
}

List Available Tools

Request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

Response (filtered by principal permissions):

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "get_project",
        "description": "GET /api/projects/:id",
        "inputSchema": { "type":"object", "properties":{ "id":{"type":"string"} },"required":["id"] },
        "annotations": { "readOnlyHint": true, "destructiveHint": false }
      }
    ]
  }
}

Call a Tool

Request:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_project",
    "arguments": { "id": "proj_123" }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type":"text", "text":"{…project JSON…}" }],
    "isError": false
  }
}

Key Implementation Files

The MCP layer spans several modules that work together to expose routes safely:

Summary

  • The Openship MCP endpoint at /api/mcp uses stateless JSON-RPC 2.0 to expose HTTP routes as AI-invokable tools.
  • Authentication leverages the same bearer token parsing as standard API requests, resolving to a principal with role, read-only status, and resource grants.
  • Tool discovery filters the route registry using filterToolsForPrincipal(), ensuring AI agents receive only permitted capabilities.
  • Execution routes requests through the full Hono middleware stack, including authMiddleware and permission checks, by using app.fetch with forwarded authorization headers.
  • No business logic is duplicated; the MCP layer acts as a thin façade over the existing REST API.

Frequently Asked Questions

What protocol does the Openship MCP endpoint use?

The endpoint implements JSON-RPC 2.0 over HTTP at the path /api/mcp. This stateless transport allows AI agents to initialize sessions, discover available tools via tools/list, and invoke them using standard JSON-RPC request/response formats.

How does the MCP layer differ from standard REST API authentication?

There is no difference in authentication logic. The MCP layer calls parseBearerToken and resolves principals using the same code path as authMiddleware. The bearer token is forwarded to internal requests via the authorization header, ensuring identical permission enforcement for AI agents and human users.

Which API routes are exposed as MCP tools?

Routes are exposed only when explicitly opted in via an mcp block in their OpenAPI specification. The getRouteRegistry function identifies these candidates, and filterToolsForPrincipal() further restricts the list based on the requesting principal's role, read-only status, and granted resource types.

What happens when an AI agent invokes a tool without proper permissions?

The dispatchTool() function creates an internal HTTP request that traverses the full middleware stack. If the bearer token lacks permission for the requested route, the standard permission layer in apps/api/src/lib/permission.ts rejects the request. The MCP wrapper captures this failure and returns a JSON-RPC response with isError: true, containing the appropriate HTTP error code and message.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →