
MintHCM MCP
MCP开源、可自托管的人力资源系统 MintHCM 自带的 MCP 模块:HR 可以让 agent 查询、统计和增删改员工档案、招聘、入离职、考勤请假、绩效等各模块记录,还能运行报表,数据留在自己的服务器上。需要先自行部署 MintHCM,首次连接时在浏览器登录授权。
Mint MCP
Mint MCP is a module in Mint that acts as an MCP server and enables integration with MCP clients such as Visual Studio Code, Copilot Chat, etc. Thanks to this, you can use MCP tools in Mint and add your own.
Authentication Flow
Mint MCP implements OAuth 2.1 with PKCE. The user-facing consent page is rendered by the Vue SPA (not by mcp/) so the experience matches the rest of Mint. The PHP shared session is used as coordination state between the MCP host and the Mint/Vue host.
Key pieces:
mcp/oauth.php— entry point for/oauth/authorize,/oauth/token, etc. Only the first redirect hits it; after that everything happens on the Mint host.OAuth2Server::redirectToLogin()— stores the pending request under$_SESSION['oauth_flows'][<flow_id>]with a 15-minuteexpires_atand a randomconsent_token, then redirects the browser to<site_url>#/mcp/login?flow=<flow_id>. Usingsite_url(notHTTP_HOST) is what lets MCP live on a separate domain.McpLoginView.vue— readsflowfrom the URL, fetchesGET /mcp/oauth-info?flow=…to populate client name and scopes, and shows explicit [Authorize] / [Cancel] buttons. No auto-authorization.McpController::authorize()/McpController::deny()— validateflow_id+consent_token(CSRF protection,hash_equals), enforce the TTL, and return the finalredirect_urlback to the MCP client.
Sequence
sequenceDiagram
participant C as MCP Client
participant B as Browser
participant MCP as mcp/oauth.php
participant Vue as Vue SPA (Mint host)
participant API as api/ (Mint host)
C->>B: redirect to /oauth/authorize?client_id=…&code_challenge=…
B->>MCP: GET /oauth/authorize
MCP->>MCP: store oauth_flows[flow_id] + consent_token
MCP-->>B: 302 to <site_url>#/mcp/login?flow=<id>
B->>Vue: load consent page
Vue->>API: GET /mcp/oauth-info?flow=<id>
API-->>Vue: { client_name, scope, consent_token }
alt not logged in
Vue->>API: POST /login (username, password)
API-->>Vue: session established
end
alt user clicks Authorize
Vue->>API: POST /mcp/authorize { flow, consent_token }
API->>MCP: completeAuthorization(flow, userId)
API-->>Vue: { redirect_url: redirect_uri?code=…&state=… }
else user clicks Cancel
Vue->>API: POST /mcp/deny { flow, consent_token }
API-->>Vue: { redirect_url: redirect_uri?error=access_denied&state=… }
end
Vue->>B: window.location = redirect_url
B->>C: redirect with code / error
C->>MCP: POST /oauth/token (code + code_verifier)
MCP-->>C: access_token, refresh_token
Security properties
consent_tokenis a 32-byte random string compared withhash_equals. Attacker JS on a third-party origin cannot read it — it is never placed in a URL, only in the JSON body returned from/mcp/oauth-infoand in the POST body to/mcp/authorize//mcp/deny./mcp/oauth-infois served withCache-Control: no-store.- Flows live for 15 minutes (
OAuth2Server::FLOW_TTL_SECONDS). Expired flows are garbage-collected on the nextredirectToLogin()call. - Concurrent flows do not collide: each authorization gets its own random
flow_idand its own slot in$_SESSION['oauth_flows']. Cancelroutes throughMcpController::deny()which re-validates the redirect_uri against the registered client, so it cannot be turned into an open redirect even if the session were tampered with.- PKCE is mandatory and only
S256is accepted —/authorizerejectscode_challenge_method=plain(OAuth2Server::PKCE_METHOD). - An authorization code can only be redeemed by the client it was issued to (
client_idchecked at/token).
Dynamic Client Registration (RFC 7591)
POST <site_url>/mcp/oauth/register is open (no authentication) so MCP clients can register themselves. ClientRegistrationValidator accepts only what the authorization code flow needs and rejects the rest with HTTP 400 (invalid_redirect_uri / invalid_client_metadata):
redirect_uris— required, non-empty; each URI must behttps://…, orhttp://on a loopback host (127.0.0.1,localhost,[::1], any port); no fragment. Custom URI schemes of native apps (e.g.myapp://) are rejected.grant_types— onlyauthorization_codeandrefresh_token(default: both).response_types— onlycode.client_name— control characters removed, truncated to 100 characters.
To switch open registration off, set in legacy/config_override.php:
$sugar_config['mcp_oauth_dynamic_registration_enabled'] = false;
/register then returns HTTP 403 access_denied and registration_endpoint disappears from the authorization server metadata. Clients must be created manually (Admin → OAuth2 Clients & Tokens).
Server Configuration
MintHCM MCP is served at <site_url>/mcp (e.g. https://your-mint-domain/mcp). Apache must have mod_rewrite loaded and AllowOverride All (or at minimum AllowOverride FileInfo) on the directory containing mcp/.
Installation requirement: the root .htaccess is generated from the template legacy/MintCLI/src/Assets/.htaccess during installation — both by MintCLI and by the browser-based installer. If for any reason the file is missing or outdated, re-run the installation's htaccess step or manually copy the template, replace __BASE_PATH__ with the installation path (e.g. /MintHCM or /), and place the result at the MintHCM root.
Discovery endpoints
OAuth 2.1 clients discover the authorization server by fetching metadata under .well-known/. Different clients use different conventions:
| URL pattern | Spec | Handler |
|---|---|---|
<site_url>/mcp/.well-known/oauth-protected-resource | RFC 9728 path-appending | mcp/.htaccess |
<site_url>/mcp/.well-known/oauth-authorization-server | RFC 8414 path-appending | mcp/.htaccess |
<site_url>/mcp/.well-known/openid-configuration | OIDC-style path-appending | mcp/.htaccess |
<host>/.well-known/oauth-authorization-server/mcp | RFC 8414 path-insertion (single instance at DocumentRoot) | root .htaccess (MintCLI) |
<host>/.well-known/oauth-protected-resource/mcp | RFC 9728 path-insertion (single instance at DocumentRoot) | root .htaccess (MintCLI) |
<host>/.well-known/openid-configuration/mcp | OIDC path-insertion (single instance at DocumentRoot) | root .htaccess (MintCLI) |
Path-insertion URLs land outside the mcp/ directory — mcp/.htaccess cannot reach them. They are handled by the root .htaccess generated by MintCLI. This rule only matches when a MintHCM installation occupies the entire DocumentRoot (no <path> segment between .well-known/... and mcp) — see below for hosts running several instances under one DocumentRoot.
Multi-instance hosting (shared DocumentRoot)
Some environments run several MintHCM installations as subdirectories of one Apache DocumentRoot, e.g.:
/var/www/ ← DocumentRoot
├── aiagent/ ← MintHCM instance #1
├── develop/ ← MintHCM instance #2
└── MintHCM/ ← MintHCM instance #3
A path-insertion discovery request for instance aiagent looks like <host>/.well-known/oauth-authorization-server/aiagent/mcp — the .well-known/ segment is resolved directly under the shared DocumentRoot, so it never reaches any instance's own .htaccess (Apache resolves .htaccess files by walking the filesystem path of the request, and aiagent here is not a path prefix — it comes after .well-known/...). No .htaccess inside aiagent/ can intercept this request.
This requires a one-time, manual addition to the .htaccess in the shared DocumentRoot itself (/var/www/.htaccess in the example above — not any instance's own .htaccess, and not tracked in any single instance's repository):
RewriteEngine On
RewriteRule ^\.well-known/oauth-protected-resource/(.+)/mcp/?$ $1/mcp/oauth.php [L,QSA,E=OAUTH_ENDPOINT:discovery]
RewriteRule ^\.well-known/oauth-authorization-server/(.+)/mcp/?$ $1/mcp/oauth.php [L,QSA,E=OAUTH_ENDPOINT:authorization]
RewriteRule ^\.well-known/openid-configuration/(.+)/mcp/?$ $1/mcp/oauth.php [L,QSA,E=OAUTH_ENDPOINT:authorization]
The (.+) capture group matches the instance's subdirectory (aiagent, develop, ...) and forwards the request into that instance's own mcp/oauth.php. Adding a new instance under a new subdirectory requires no further Apache changes — the rule is generic. This block is independent of, and does not replace, the per-instance .htaccess generated by MintCLI.
MCP Client Configuration
The following example describes configuration for the MCP client in Visual Studio Code; it should be similar for other clients.
Adding MCP Server in Visual Studio Code
- Run command
MCP: Add Server...in Visual Studio Code. - Choose HTTP as the server type.
- Enter the URL of your Mint MCP server, e.g.,
https://your-mint-domain/mcp. - Enter a name for the server, e.g.,
my-mcp-server-mint1. Your configuration should look like this in yourmcp.jsonfile:
{
"servers": {
"my-mcp-server-mint1": {
"type": "http",
"url": "https://your-mint-domain/mcp"
}
}
}
Using the MCP Server in Visual Studio Code
- Run the command
MCP: List Serversand select your servermy-mcp-server-mint1. - Select
Start server. If this is your first time using Mint MCP via this client, you will be asked to log in to your MintHCM. After a successful login wait for the messageConnection state: Runningand thenDiscovered X tools. - Open Copilot Chat, set the mode to
Agent, and select"Configure tools..."(the tools icon under the chat input field). - Make sure that in the
my-mcp-server-mint1section, the tools you want to use are checked.
After completing these steps, your MCP client should be properly configured and ready to use.
Adding MCP Server in Claude Code CLI
- Register the server:
claude mcp add --transport http mint-mcp https://your-mint-domain/mcp/ - Start Claude Code:
claude - On first connection Claude Code will open a browser tab with the Mint login and consent screen. Log in and click Authorize.
- After authorization, verify the connection:
You should see/mcpmint-mcpwith statusconnectedand the list of discovered tools.
To remove the server: claude mcp remove mint-mcp
Claude Code CLI inside a local Docker container
When Claude Code CLI runs inside the same Docker container as MintHCM (typical local dev setup), a few things differ from a remote deployment.
MCP URL to use
Inside the container Apache listens on port 80 — use the internal URL, not the Windows-side mapped port:
http://localhost/mint/mcp/
site_url mismatch
MintHCM's site_url in legacy/config.php is typically set to the Windows-side URL (e.g. http://localhost:8085/mint). The OAuth discovery documents return that URL in authorization_endpoint, token_endpoint, etc. From inside the container port 8085 is unreachable — token exchange fails.
Two options:
- Run Claude Code on Windows (recommended) — use
http://localhost:8085/mint/mcpwhere all OAuth endpoints are reachable. - Change
site_urltohttp://localhost/mintinlegacy/config_override.php— discovery returns internal URLs, OAuth works inside the container, but browser redirects during login also uselocalhost(reachable from Windows via the same port-mapping).
Steps
- Register and start the server:
claude mcp add --transport http mint-mcp http://localhost/mint/mcp/ claude - Claude Code prints an OAuth authorization URL — open it in the browser on Windows, log in to Mint and click
Authorize.
- If you changed
site_urltohttp://localhost/mint, the browser will redirect tohttp://localhost/mint/mcp/oauth/callback?code=…&state=…. Changelocalhosttolocalhost:8085(use your port) in the URL and hit Enter to reach the MintHCM host on Windows. The callback will complete the OAuth flow.
- If you changed
- Verify the connection: type
/mcpin Claude Code —mint-mcpshould show statusconnected.
Troubleshooting — POST /mcp returns 301 then GET returns 405/404
Check added url to claude mcp add command. It should be the same as the one in the mcp.json file. If you added http://localhost/mint/mcp without the trailing slash, the client will POST to http://localhost/mint/mcp (no slash) which Apache redirects to http://localhost/mint/mcp/ (with slash). The client then GETs the new URL, which is not a valid endpoint and returns 405 or 404. Always add the trailing slash when adding the server URL to the client, correct example: http://localhost/mint/mcp/
**Troubleshooting — discovery returns `localhost:8085` URLs but client can't reach them**
The `site_url` mismatch described above. Either switch to running Claude Code on Windows or change `site_url`.
---
### Troubleshooting
Start with `mcp/mintmcp.log` — it records key OAuth lifecycle events (client registered, token issued, error responses) without any secrets. Then compare with Apache's `access.log` to see URLs the client hit *before* reaching PHP.
**"Dynamic Client Registration not supported" popup, or `SDK auth failed: HTTP 404` with HTML body in the error message**
The client requested a `.well-known/...` URL that Apache served 404 HTML for, and the client tried to parse it as JSON.
1. Check `AllowOverride All` (or `FileInfo`) is set for the `mcp/` directory in Apache's vhost config — without it, `.htaccess` is ignored entirely.
2. Fire a curl at the discovery URL — e.g. `curl -i https://your-mint-domain/mcp/.well-known/oauth-authorization-server`. Expect `200 OK` with JSON; `404` means the rewrite rule isn't matching.
**Connection stuck at "Waiting for server to respond to `initialize` request..."**
The client is waiting for you to complete the browser login. If no browser tab opened, clear the client's cached auth state:
- **VS Code**: Command Palette → MCP: List Servers → *your server* → Sign Out (if available).
- **Claude Code CLI**: `claude mcp remove <server-name>` and re-add it.
- **Any client**: close it, remove the stored credential entry (Windows Credential Manager / macOS Keychain / `~/.claude.json`), restart.
**`POST /register` returns 404 in Apache access log (without `/mcp/oauth/register`)**
The client is using hard-coded RFC 7591 defaults because it couldn't read `registration_endpoint` from the authorization server metadata — which means discovery failed earlier. Same fix as the "Dynamic Client Registration not supported" case above.
**Nothing shows up in `mintmcp.log` after connecting**
The request isn't reaching PHP. Check Apache `access.log` to see what URL the client hit and what status it got. If the URL is correct but returns 404, `AllowOverride` is likely too restrictive in vhost config.
## MCP Client Compatibility
Different MCP clients implement different subsets of the OAuth 2.1 and discovery specs. Below is what was tested, what the root cause of each quirk is, and what was done to address it.
### VS Code Extension (GitHub Copilot)
**Status: ✅ Supported**
VS Code uses path-appending discovery exclusively: it queries `<mcp-url>/.well-known/oauth-protected-resource`, then falls back to `<mcp-url>/.well-known/oauth-authorization-server` and `<mcp-url>/.well-known/openid-configuration`. All three are handled by `mcp/.htaccess` and require no root `.htaccess` rules.
### Claude Code CLI
**Status: ✅ Supported** (requires root `.htaccess` generated by MintCLI)
Claude Code CLI embeds the Bun runtime which implements a stricter OAuth 2.1 discovery flow. Two issues were found and fixed:
**Issue 1 — Path-insertion discovery returns HTML instead of 404**
Bun tries RFC 8414/RFC 9728 path-insertion URLs before the MCP request:
/.well-known/oauth-authorization-server/ /.well-known/oauth-protected-resource/
These URLs have `.well-known/` at the host root, outside the `mcp/` directory. If the root `.htaccess` has a catch-all Vue route without an exception, these URLs return `200 + HTML` instead of `404`. Bun does not fall back to path-appending when it receives a `200` — it considers discovery done but fails to parse the HTML as JSON and silently aborts the flow (no browser popup, no error message).
Fix: root `.htaccess` includes explicit `RewriteRule ^\.well-known/...mcp/?$` rules that route to `mcp/oauth.php`, plus a `[R=404]` catch-all for all other `.well-known/` paths so Bun can fall back correctly.
**Issue 2 — OIDC Discovery fields missing from authorization server metadata**
Bun validates the authorization server metadata document against the OIDC Discovery 1.0 schema (Zod). Two fields are required by the OIDC spec but were missing from `handleAuthorization()`:
- `subject_types_supported` — must be an array, e.g. `["public"]`
- `id_token_signing_alg_values_supported` — must be an array, e.g. `["RS256"]`
Without these fields, Bun's Zod validation throws and the client silently aborts — same symptom as Issue 1 (no browser popup). The error is only visible if you run Claude Code with `--debug`.
Fix: both fields are now returned from `handleAuthorization()`.
### Postman
**Status: ✅ Supported** (tested on Postman 12.17.3)
#### Prerequisites — site_url and frontend build
Two things must be true before the Postman login page works correctly:
1. **`site_url` in `legacy/config.php` must match the base URL you use in Postman.** The OAuth consent page redirect uses `site_url` — if it points to a different host or port than what Postman sends requests to, the browser will open the wrong address. This matters most for local Docker setups where `site_url` is often set to the Windows-side mapped port (e.g. `http://localhost:8085/mint`) but Apache inside the container listens on port 80.
2. **The Vue frontend must be built** (`npm run build:repo`), not running via the Vite dev server. Postman opens the consent page in a browser; the Vite dev server is not a reliable target for that browser session and may not serve the page correctly.
#### Step 1 — Register an OAuth client
A client with Postman's callback URL must exist in MintHCM before the first login. Replace `<site_url>` with the value from `legacy/config.php` (e.g. `http://localhost:8085/mint`).
- **Register via DCR** — send a `POST` to `<site_url>/mcp/oauth/register` (no auth required):
```json
{
"client_name": "Postman",
"redirect_uris": ["https://oauth.pstmn.io/v1/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}
The response contains client_id — copy it for the next step.
- Register manually — go to MintHCM → Admin → OAuth2 Clients & Tokens, create a new client and set
Redirect URLto exactlyhttps://oauth.pstmn.io/v1/callback.
Step 2 — Add an MCP request in Postman
In Postman, create a new request and set its type to MCP (available in the request type dropdown next to the URL bar). Enter the server URL:
<site_url>/mcp/
Go to the Authorization tab, select OAuth 2.0, and fill in the fields:
| Field | Value |
|---|---|
| Grant type | Authorization Code (with PKCE) |
| Auth URL | <site_url>/mcp/oauth/authorize |
| Access Token URL | <site_url>/mcp/oauth/token |
| Client ID | (client_id from Step 1) |
| Callback URL | https://oauth.pstmn.io/v1/callback |
| Scope | mcp:read mcp:write |
| Code Challenge Method | SHA-256 |
Click Get New Access Token. A browser window opens with the MintHCM login and consent screen — log in and click Authorize. Postman receives the token automatically.
Note: Do not use
Client Credentialsgrant type — it is not supported. Do not point the token URL atlegacy/Api/...— the MCP OAuth server is at/mcp/oauth/token.
Step 3 — Connect and discover tools
With the token set, send a GET request to <site_url>/mcp/ — the server returns a list of all available MCP tools. This confirms the connection is working and the token is valid.
Internal session token
The route internal/session-token allows CORS from the app origin (site_url) and the local Vite dev server (http://localhost:5173). Both are listed in Auth/Internal/TrustedOrigins.php.
MCP Apps
This server supports the MCP Apps extension and allows attaching UI resources to tool responses via _meta. Each app is loaded in a sandbox; tools can return structured data (e.g. structuredContent) for the app to render.
Tool response with UI resource metadata
When a tool has a registered UI resource, tools/call returns normal content and _meta with the linked ui:// URI. The app receives the tool result (including any structuredContent) and can render it.
Example:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "..." }],
"structuredContent": { "columns": ["name", "id"], "records": [] },
"_meta": {
"ui": {
"resourceUri": "ui://mint-mcp/search-records"
}
}
}
}
Adding a UI resource to a tool
Use a dedicated resource class in Capabilities/Resources/ with the #[McpResource] attribute, then link the tool to that resource via #[McpTool(meta: ...)].
Example resource class:
namespace MintMCP\Capabilities\Resources;
use Mcp\Capability\Attribute\McpResource;
final class SearchRecordsResource extends AbstractMCPAppResource
{
public const URI = 'ui://mint-mcp/search-records';
#[McpResource(
uri: self::URI,
name: 'search_records_app',
description: 'Table view for search_records results with links to each record.',
mimeType: 'text/html;profile=mcp-app',
meta: [
'ui' => [
'prefersBorder' => true,
],
],
)]
public function getSearchRecordsApp(): string
{
return (string) file_get_contents(self::appDistPath('search-records'));
}
}
Example tool linking to the resource:
use Mcp\Capability\Attribute\McpTool;
use MintMCP\Capabilities\Resources\SearchRecordsResource;
#[McpTool(
name: 'search_records',
description: 'Retrieve records from a MintHCM module.',
meta: [
'ui' => [
'resourceUri' => SearchRecordsResource::URI,
'visibility' => ['model', 'app'],
],
],
)]
resourceUri should follow the ui://mint-mcp/<tool-name> convention used by Mint MCP resources.
Developing and building MCP Apps
From mcp/apps/:
npm run "dev mcp-apps"— Vite dev server with HMR. Open an app athttp://localhost:5173/<app-dir>/<app-file>.html(e.g.http://localhost:5173/search-records/search-records.html).npm run "build mcp-apps"— Type-check, build, and flatten to single-file HTML inmcp/apps/dist/(e.g.search-records.html).
When rendering server-supplied data (e.g. structuredContent.columns, record fields), use safe DOM APIs (createElement, textContent, replaceChildren) instead of innerHTML to avoid XSS, even when the server is trusted.
For the full spec: MCP Apps (ext-apps).
相關資源

OneWave AI Claude Skills
Skill两百多个单文件技能,以销售、营销、咨询和运营为主:成交策略、销售预测、开发信、咨询方案与工作说明书、合同条款审阅、简历筛选、报销核对、招标应答等,也有个人理财与求职类技能。合同、财税相关结果需专业人员复核。
OneWave-AI/claude-skills430
Apideck MCP Server
MCPApideck 官方 MCP:用一套接口读写 QuickBooks、Xero、NetSuite 等会计系统的发票、账目与报表,以及 HR、CRM、招聘系统的数据,适合同时管理多套系统的财务或代账团队。需要 Apideck 付费账号(可免费试用 30 天)、API Key 与 App ID,并先在 Apideck 里连好自己的系统;报表结论需会计复核。
apideck-libraries/mcp21
Himalayas Remote Jobs MCP Server
MCP远程招聘平台 Himalayas 的官方远程 MCP:招聘方可以用对话免费发布远程职位(可附筛选题)、搜索开放求职的远程候选人、查各岗位和国家的薪资基准;求职者可以搜职位、管理投递看板和个人资料。搜职位、候选人、公司和薪资不用账号;管理个人资料、投递看板和公司职位要在浏览器登录 Himalayas 账号。
Himalayas-App/himalayas-mcp21
100Hires MCP Server
MCP招聘管理系统(ATS)100Hires 的官方托管 MCP:HR 可以用对话管理候选人、职位发布、招聘流程阶段、面试安排、邮件模板和面试评估,覆盖它的完整 API。在浏览器里 OAuth 登录就能用,不用本地安装;需要 100Hires 付费账号,可以先免费试用 14 天。
100Hires/mcp0
team-composition-analysis
Skill为种子轮到 A 轮的创业公司规划团队:各阶段该招哪些岗位、先招谁、组织架构怎么搭,以及薪酬、期权区间和人力预算。薪酬基准取自 2024 年美国市场,实际方案需结合本地行情并请财务、法务专业人员复核。
wshobson/agents42.5k
Employment Contract Templates
Skill提供录用通知书、劳动合同、员工手册制度条款等模板和起草要点,帮 HR 与法务快速写出格式统一的用工文书。模板按美国用工规则编写,国内使用需按本地劳动法改写,成稿须经律师复核。无需账号或 API Key。
wshobson/agents42.5k