让 AI 大模型成为您的开票助手 — 通过 MCP (Model Context Protocol) 协议,将电子发票/数电发票 API 接入 Claude、GPT、Cursor、Cherry Studio 等大模型平台,实现 AI 智能开票。
MCP(Model Context Protocol)是由 Anthropic 推出的开放标准协议,定义了 AI 大模型如何安全、标准化地调用外部工具和 API。
fa-piao.com 率先实现了 MCP Server (Streamable HTTP),让任何支持 MCP 的大模型都能直接操作发票系统:
- Streamable HTTP 传输,实时流式响应
- 完整的 Tools 定义:开票 / 查询 / 红冲 / 查验
- 自然语言交互,无需编写代码
- 企业级 API Key 权限隔离
- 支持所有 MCP 兼容的大模型客户端
| 特性 | 说明 |
|---|---|
| 🔌 Streamable HTTP | 基于 HTTP 的流式传输协议,实时推送响应 |
| 🛠️ 完整 Tools 定义 | 开票、查询、红冲、查验等全部发票操作 |
| 🔐 安全认证 | Bearer Token 认证 + TLS 1.3 加密传输 |
| 🌍 多语言支持 | Python / Node.js / Java / Go / C# / PHP / Rust |
| 🤖 大模型兼容 | Claude / GPT / Cursor / Cherry Studio / TRAE |
| 📊 调试模式 | 内置 DEBUG 开关,完整请求/响应日志 |
访问 open.fa-piao.com 注册并登录,申请 MCP Token。
在大模型客户端的配置文件中添加以下 JSON:
{
"mcpServers": {
"tax-invoice-mcp": {
"type": "streamableHttp",
"url": "https://mcp.fa-piao.com",
"headers": {
"Authorization": "Bearer YOUR_MCP_TOKEN",
"X-Client-Version": "1.0.1"
}
}
}
}
⚠️ 重要:请将YOUR_MCP_TOKEN替换为实际的 MCP Token。
配置完成后,即可通过自然语言与大模型交互,例如:
- "查询纳税人 91500112MAXXXXX 的开票状态"
- "查验这张发票的真伪"
| 配置项 | 值 | 说明 |
|---|---|---|
| 协议类型 | streamableHttp |
Streamable HTTP 传输协议 |
| 端点地址 | https://mcp.fa-piao.com |
MCP Server 服务地址 |
| 认证方式 | Bearer Token |
在 Header 中携带 Authorization |
| 客户端版本 | 1.0.1 |
X-Client-Version 请求头 |
| Header | 必填 | 说明 |
|---|---|---|
Authorization |
✅ | Bearer Token 认证 |
Content-Type |
✅ | application/json |
X-Client-Version |
✅ | 客户端版本号 |
Accept |
流式必填 | text/event-stream, application/json |
mcp-session-id |
自动 | 服务端返回的会话 ID |
所有示例均基于 JSON-RPC 2.0 协议,支持流式和非流式两种调用模式。
流式调用
import requests
import json
import time
from datetime import datetime
MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"
DEBUG = True
mcp_session_id = None
request_id_counter = 0
def get_next_request_id():
global request_id_counter
request_id_counter += 1
return request_id_counter
def mcp_call_stream(method, params=None):
global mcp_session_id
start_time = time.time()
current_id = get_next_request_id()
payload = {
"jsonrpc": "2.0",
"id": current_id,
"method": method,
"params": params if params is not None else {}
}
body = json.dumps(payload, ensure_ascii=False)
headers = {
"Authorization": f"Bearer {MCP_TOKEN}",
"Content-Type": "application/json",
"X-Client-Version": "1.0.1",
"Accept": "text/event-stream, application/json"
}
if mcp_session_id:
headers["mcp-session-id"] = mcp_session_id
try:
resp = requests.post(MCP_URL, data=body, headers=headers, timeout=120)
duration_ms = (time.time() - start_time) * 1000
response_session_id = resp.headers.get("mcp-session-id")
if response_session_id:
mcp_session_id = response_session_id
content_type = resp.headers.get("Content-Type", "")
is_sse = "text/event-stream" in content_type
if is_sse:
response_data = []
for line in resp.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("data:"):
data = line[5:].strip()
if data == "[DONE]":
break
response_data.append(data)
return "\n".join(response_data)
else:
return resp.text.strip()
except Exception as e:
print(f"[异常] {method}: {e}")
return None
if __name__ == "__main__":
mcp_call_stream("initialize", {"protocolVersion": "2025-11-25"})
mcp_call_stream("notifications/initialized", {})
mcp_call_stream("tools/list", {})
mcp_call_stream("tools/call", {
"name": "enterprise_query_state",
"arguments": {"nsrsbh": "91500112MAXXXXX", "username": "1325580xxxx"}
})非流式调用
import requests
import json
MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"
mcp_session_id = None
request_id_counter = 0
def get_next_request_id():
global request_id_counter
request_id_counter += 1
return request_id_counter
def mcp_call(method, params=None):
global mcp_session_id
current_id = get_next_request_id()
payload = {
"jsonrpc": "2.0",
"id": current_id,
"method": method,
"params": params if params is not None else {}
}
headers = {
"Authorization": f"Bearer {MCP_TOKEN}",
"X-Client-Version": "1.0.1",
"Content-Type": "application/json"
}
if mcp_session_id:
headers["mcp-session-id"] = mcp_session_id
resp = requests.post(MCP_URL, data=json.dumps(payload, ensure_ascii=False),
headers=headers, timeout=120)
response_session_id = resp.headers.get("mcp-session-id")
if response_session_id:
mcp_session_id = response_session_id
return resp.text.strip()
if __name__ == "__main__":
mcp_call("initialize", {"protocolVersion": "2025-11-25"})
result = mcp_call("tools/call", {
"name": "enterprise_query_state",
"arguments": {"nsrsbh": "91500112MAXXXX", "username": "1325580xxxx"}
})
print(json.dumps(result, indent=2, ensure_ascii=False))流式调用
import axios, { AxiosResponse } from 'axios';
const MCP_URL = 'https://mcp.fa-piao.com';
const MCP_TOKEN = 'YOUR_MCP_TOKEN';
const DEBUG = true;
let mcpSessionId: string | null = null;
let requestIdCounter = 0;
function getNextRequestId(): number {
requestIdCounter += 1;
return requestIdCounter;
}
function parseSseResponse(responseText: string): string {
const lines = responseText.split(/\r?\n/);
const responseData: string[] = [];
for (const line of lines) {
if (!line.trim()) continue;
if (line.startsWith('data:')) {
const data = line.substring(5).trim();
if (data === '[DONE]') break;
responseData.push(data);
}
}
return responseData.join('\n');
}
export async function mcpCallStream(method: string, params?: any): Promise<string | null> {
const startTime = Date.now();
const currentId = getNextRequestId();
const payload = {
jsonrpc: '2.0',
id: currentId,
method: method,
params: params !== undefined && params !== null ? params : {}
};
const headers: Record<string, string> = {
'Authorization': `Bearer ${MCP_TOKEN}`,
'Content-Type': 'application/json',
'X-Client-Version': '1.0.1',
'Accept': 'text/event-stream, application/json'
};
if (mcpSessionId) {
headers['mcp-session-id'] = mcpSessionId;
}
try {
const response: AxiosResponse = await axios.post(MCP_URL, JSON.stringify(payload), {
headers: headers,
timeout: 120000,
validateStatus: () => true,
responseType: 'text',
transformResponse: [(data) => data]
});
const responseSessionId = response.headers['mcp-session-id'];
if (responseSessionId && !Array.isArray(responseSessionId)) {
mcpSessionId = responseSessionId;
}
const contentType = response.headers['content-type'] || '';
const isSse = contentType.includes('text/event-stream');
if (isSse) {
return parseSseResponse(response.data);
}
return response.data.trim();
} catch (error) {
console.error(`[异常] ${method}:`, error);
return null;
}
}
async function main() {
await mcpCallStream('initialize', { protocolVersion: '2025-11-25' });
await mcpCallStream('notifications/initialized', {});
await mcpCallStream('tools/list', {});
await mcpCallStream('tools/call', {
name: 'enterprise_query_state',
arguments: { nsrsbh: '91500112MAXXXXX', username: '1325580xxxx' }
});
}
if (require.main === module) {
main().catch(console.error);
}非流式调用
import axios, { AxiosResponse } from 'axios';
const MCP_URL = 'https://mcp.fa-piao.com';
const MCP_TOKEN = 'YOUR_MCP_TOKEN';
let mcpSessionId: string | null = null;
let requestId = 0;
function nextRequestId(): number {
return ++requestId;
}
export async function mcpCall(method: string, params?: string): Promise<string> {
const currentId = nextRequestId();
const body = JSON.stringify({
jsonrpc: '2.0',
id: currentId,
method: method,
params: params ? JSON.parse(params) : {}
});
const headers: Record<string, string> = {
'Authorization': `Bearer ${MCP_TOKEN}`,
'X-Client-Version': '1.0.1',
'Content-Type': 'application/json'
};
if (mcpSessionId) {
headers['mcp-session-id'] = mcpSessionId;
}
const response: AxiosResponse = await axios.post(MCP_URL, body, {
headers: headers,
timeout: 30000,
validateStatus: () => true,
transformResponse: []
});
const responseSessionId = response.headers['mcp-session-id'];
if (responseSessionId && !Array.isArray(responseSessionId)) {
mcpSessionId = responseSessionId;
}
return typeof response.data === 'string' ? response.data : JSON.stringify(response.data);
}
async function main() {
await mcpCall('initialize', '{"protocolVersion": "2025-11-25"}');
const result = await mcpCall('tools/call',
'{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}');
console.log(result);
}
if (require.main === module) {
main().catch(console.error);
}流式调用
import java.net.URI;
import java.net.http.*;
import java.io.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class McpStreamClient {
public static final boolean DEBUG = false;
private static final String MCP_URL = "https://mcp.fa-piao.com";
private static final String MCP_TOKEN = "YOUR_MCP_TOKEN";
private static final AtomicReference<String> mcpSessionId = new AtomicReference<>(null);
private static final AtomicInteger requestId = new AtomicInteger(0);
private static int nextRequestId() {
return requestId.incrementAndGet();
}
public static String mcpCallStream(String method, String params) throws Exception {
int currentId = nextRequestId();
String body = String.format(
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"%s\",\"params\":%s}",
currentId, method, params != null ? params : "{}"
);
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(MCP_URL))
.header("Authorization", "Bearer " + MCP_TOKEN)
.header("X-Client-Version", "1.0.1")
.header("Content-Type", "application/json")
.header("Accept", "text/event-stream, application/json")
.POST(HttpRequest.BodyPublishers.ofString(body));
String sessionId = mcpSessionId.get();
if (sessionId != null && !sessionId.isEmpty()) {
requestBuilder.header("mcp-session-id", sessionId);
}
HttpClient client = HttpClient.newHttpClient();
HttpResponse<InputStream> resp = client.send(
requestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream());
String responseSessionId = resp.headers().firstValue("mcp-session-id").orElse(null);
if (responseSessionId != null) {
mcpSessionId.set(responseSessionId);
}
return readStreamResponse(resp.body(), resp.headers());
}
private static String readStreamResponse(InputStream inputStream, HttpHeaders respHeaders)
throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder responseBody = new StringBuilder();
String line;
String contentType = respHeaders.firstValue("Content-Type").orElse("");
boolean isSSE = contentType.contains("text/event-stream");
if (isSSE) {
while ((line = reader.readLine()) != null) {
if (line.startsWith("data:")) {
String data = line.substring(5).trim();
if ("[DONE]".equals(data)) break;
responseBody.append(data);
}
}
} else {
int ch;
while ((ch = reader.read()) != -1) {
responseBody.append((char) ch);
}
}
reader.close();
return responseBody.toString().trim();
}
public static void main(String[] args) throws Exception {
mcpCallStream("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
mcpCallStream("notifications/initialized", "{}");
mcpCallStream("tools/list", "{}");
mcpCallStream("tools/call",
"{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXXX\",\"username\":\"1325580xxxx\"}}");
}
}非流式调用
import java.net.URI;
import java.net.http.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class McpClient {
private static final String MCP_URL = "https://mcp.fa-piao.com";
private static final String MCP_TOKEN = "YOUR_MCP_TOKEN";
private static final AtomicReference<String> mcpSessionId = new AtomicReference<>(null);
private static final AtomicInteger requestId = new AtomicInteger(0);
private static int nextRequestId() {
return requestId.incrementAndGet();
}
public static String mcpCall(String method, String params) throws Exception {
int currentId = nextRequestId();
String body = String.format(
"{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"%s\",\"params\":%s}",
currentId, method, params != null ? params : "{}"
);
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create(MCP_URL))
.header("Authorization", "Bearer " + MCP_TOKEN)
.header("X-Client-Version", "1.0.1")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body));
String sessionId = mcpSessionId.get();
if (sessionId != null && !sessionId.isEmpty()) {
requestBuilder.header("mcp-session-id", sessionId);
}
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> resp = client.send(
requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
String responseSessionId = resp.headers().firstValue("mcp-session-id").orElse(null);
if (responseSessionId != null) {
mcpSessionId.set(responseSessionId);
}
return resp.body();
}
public static void main(String[] args) throws Exception {
mcpCall("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
mcpCall("tools/call",
"{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXX\",\"username\":\"1325580xxxx\"}}");
}
}流式调用
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"strings"
"sync/atomic"
)
const (
MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"
DEBUG = true
)
var (
mcpSessionId atomic.Value
requestId atomic.Int32
)
func init() {
mcpSessionId.Store("")
}
func nextRequestId() int32 {
return requestId.Add(1)
}
func mcpCallStream(method string, params string) (string, error) {
currentId := nextRequestId()
body := fmt.Sprintf(`{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}`,
currentId, method, params)
req, err := http.NewRequest("POST", MCP_URL, bytes.NewBufferString(body))
if err != nil {
return "", fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Authorization", "Bearer "+MCP_TOKEN)
req.Header.Set("X-Client-Version", "1.0.1")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "text/event-stream, application/json")
sessionId := mcpSessionId.Load().(string)
if sessionId != "" {
req.Header.Set("mcp-session-id", sessionId)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
responseSessionId := resp.Header.Get("mcp-session-id")
if responseSessionId != "" {
mcpSessionId.Store(responseSessionId)
}
return readStreamResponse(resp.Body, resp.Header.Get("Content-Type"))
}
func readStreamResponse(body io.Reader, contentType string) (string, error) {
reader := bufio.NewReader(body)
var responseBody strings.Builder
isSSE := strings.Contains(contentType, "text/event-stream")
if isSSE {
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return "", err
}
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "data:") {
data := strings.TrimSpace(line[5:])
if data == "[DONE]" {
break
}
responseBody.WriteString(data)
}
}
} else {
all, err := io.ReadAll(reader)
if err != nil {
return "", err
}
responseBody.Write(all)
}
return strings.TrimSpace(responseBody.String()), nil
}
func main() {
mcpCallStream("initialize", `{"protocolVersion": "2025-11-25"}`)
mcpCallStream("notifications/initialized", "{}")
mcpCallStream("tools/list", "{}")
mcpCallStream("tools/call",
`{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXXX","username":"1325580xxxx"}}`)
}非流式调用
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"sync/atomic"
)
const (
MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"
)
var (
mcpSessionId atomic.Value
requestId atomic.Int32
)
func init() {
mcpSessionId.Store("")
}
func nextRequestId() int32 {
return requestId.Add(1)
}
func mcpCall(method string, params string) (string, error) {
currentId := nextRequestId()
body := fmt.Sprintf(`{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}`,
currentId, method, params)
req, err := http.NewRequest("POST", MCP_URL, bytes.NewBufferString(body))
if err != nil {
return "", fmt.Errorf("创建请求失败: %v", err)
}
req.Header.Set("Authorization", "Bearer "+MCP_TOKEN)
req.Header.Set("X-Client-Version", "1.0.1")
req.Header.Set("Content-Type", "application/json")
sessionId := mcpSessionId.Load().(string)
if sessionId != "" {
req.Header.Set("mcp-session-id", sessionId)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
responseSessionId := resp.Header.Get("mcp-session-id")
if responseSessionId != "" {
mcpSessionId.Store(responseSessionId)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
return string(respBody), nil
}
func main() {
mcpCall("initialize", `{"protocolVersion": "2025-11-25"}`)
mcpCall("tools/call",
`{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}`)
}流式调用
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Tax.Invoice.Example
{
public class McpStreamClient
{
public static readonly bool DEBUG = true;
private const string MCP_URL = "https://mcp.fa-piao.com";
private const string MCP_TOKEN = "YOUR_MCP_TOKEN";
private static string? mcpSessionId = null;
private static int requestId = 0;
private static readonly object lockObj = new object();
private static int NextRequestId()
{
lock (lockObj) { return ++requestId; }
}
public static async Task<string> McpCallStream(string method, string? paramsJson = null)
{
int currentId = NextRequestId();
string body = string.Format(
"{{\"jsonrpc\":\"2.0\",\"id\":{0},\"method\":\"{1}\",\"params\":{2}}}",
currentId, method, !string.IsNullOrEmpty(paramsJson) ? paramsJson : "{}"
);
using var requestBuilder = new HttpRequestMessage(HttpMethod.Post, MCP_URL);
requestBuilder.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", MCP_TOKEN);
requestBuilder.Headers.TryAddWithoutValidation("X-Client-Version", "1.0.1");
requestBuilder.Headers.TryAddWithoutValidation("Accept", "text/event-stream, application/json");
if (!string.IsNullOrEmpty(mcpSessionId))
requestBuilder.Headers.TryAddWithoutValidation("mcp-session-id", mcpSessionId);
requestBuilder.Content = new StringContent(body, Encoding.UTF8, "application/json");
using var client = new HttpClient();
var response = await client.SendAsync(requestBuilder);
var responseSessionId = response.Headers.GetValues("mcp-session-id").FirstOrDefault();
if (!string.IsNullOrEmpty(responseSessionId))
mcpSessionId = responseSessionId;
return await ReadStreamResponse(
await response.Content.ReadAsStreamAsync(),
response.Content.Headers.ContentType?.MediaType ?? "");
}
private static async Task<string> ReadStreamResponse(Stream stream, string contentType)
{
using var reader = new StreamReader(stream, Encoding.UTF8);
var responseBody = new StringBuilder();
bool isSSE = contentType.Contains("text/event-stream");
if (isSSE)
{
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.StartsWith("data:"))
{
string data = line.Substring(5).Trim();
if ("[DONE]".Equals(data)) break;
responseBody.Append(data);
}
}
}
else
{
responseBody.Append(await reader.ReadToEndAsync());
}
return responseBody.ToString().Trim();
}
public static async Task Main(string[] args)
{
await McpCallStream("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
await McpCallStream("notifications/initialized", "{}");
await McpCallStream("tools/list", "{}");
await McpCallStream("tools/call",
"{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXXX\",\"username\":\"1325580xxxx\"}}");
}
}
}非流式调用
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Tax.Invoice.Example
{
public class McpClient
{
private const string MCP_URL = "https://mcp.fa-piao.com";
private const string MCP_TOKEN = "YOUR_MCP_TOKEN";
private static string? mcpSessionId = null;
private static int requestId = 0;
private static readonly object lockObj = new object();
private static int NextRequestId()
{
lock (lockObj) { return ++requestId; }
}
public static async Task<string> McpCall(string method, string? paramsJson = null)
{
int currentId = NextRequestId();
string body = string.Format(
"{{\"jsonrpc\":\"2.0\",\"id\":{0},\"method\":\"{1}\",\"params\":{2}}}",
currentId, method, !string.IsNullOrEmpty(paramsJson) ? paramsJson : "{}"
);
using var requestBuilder = new HttpRequestMessage(HttpMethod.Post, MCP_URL);
requestBuilder.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", MCP_TOKEN);
requestBuilder.Headers.TryAddWithoutValidation("X-Client-Version", "1.0.1");
if (!string.IsNullOrEmpty(mcpSessionId))
requestBuilder.Headers.TryAddWithoutValidation("mcp-session-id", mcpSessionId);
requestBuilder.Content = new StringContent(body, Encoding.UTF8, "application/json");
using var client = new HttpClient();
var response = await client.SendAsync(requestBuilder);
var responseSessionId = response.Headers.GetValues("mcp-session-id").FirstOrDefault();
if (!string.IsNullOrEmpty(responseSessionId))
mcpSessionId = responseSessionId;
return await response.Content.ReadAsStringAsync();
}
public static async Task Main(string[] args)
{
await McpCall("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
await McpCall("tools/call",
"{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXX\",\"username\":\"1325580xxxx\"}}");
}
}
}流式调用
<?php
class McpStreamClient
{
const DEBUG = false;
private static $MCP_URL = "https://mcp.fa-piao.com";
private static $MCP_TOKEN = "YOUR_MCP_TOKEN";
private static $mcpSessionId = null;
private static $requestId = 0;
private static function nextRequestId()
{
return ++self::$requestId;
}
public static function mcpCallStream($method, $params = null)
{
$currentId = self::nextRequestId();
$paramsJson = $params !== null ? $params : '{}';
$body = json_encode([
'jsonrpc' => '2.0',
'id' => $currentId,
'method' => $method,
'params' => json_decode($paramsJson, true)
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$headers = [
'Authorization: Bearer ' . self::$MCP_TOKEN,
'X-Client-Version: 1.0.1',
'Content-Type: application/json',
'Accept: text/event-stream, application/json'
];
if (self::$mcpSessionId !== null && self::$mcpSessionId !== '') {
$headers[] = 'mcp-session-id: ' . self::$mcpSessionId;
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => self::$MCP_URL,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
if (curl_errno($ch)) {
curl_close($ch);
throw new Exception("cURL Error: " . curl_error($ch));
}
curl_close($ch);
$responseHeaders = substr($response, 0, $headerSize);
$responseBody = trim(substr($response, $headerSize));
preg_match_all('/^mcp-session-id:\s*(.+)$/im', $responseHeaders, $matches);
if (!empty($matches[1])) {
self::$mcpSessionId = trim($matches[1][0]);
}
return $responseBody;
}
public static function initialize()
{
return self::mcpCallStream("initialize", '{"protocolVersion": "2025-11-25"}');
}
public static function toolsCall($toolName, $arguments)
{
$params = json_encode(['name' => $toolName, 'arguments' => $arguments],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return self::mcpCallStream("tools/call", $params);
}
}
// 使用示例
McpStreamClient::initialize();
McpStreamClient::toolsCall("enterprise_query_state", [
"nsrsbh" => "91500112MAXXXXX",
"username" => "1325580xxxx"
]);非流式调用
<?php
class McpClient
{
const DEBUG = true;
private static $MCP_URL = "https://mcp.fa-piao.com";
private static $MCP_TOKEN = "YOUR_MCP_TOKEN";
private static $mcpSessionId = null;
private static $requestId = 0;
private static function nextRequestId()
{
return ++self::$requestId;
}
public static function mcpCall($method, $params = null)
{
$currentId = self::nextRequestId();
$paramsJson = $params !== null ? $params : '{}';
$body = json_encode([
'jsonrpc' => '2.0',
'id' => $currentId,
'method' => $method,
'params' => json_decode($paramsJson, true)
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$headers = [
'Authorization: Bearer ' . self::$MCP_TOKEN,
'X-Client-Version: 1.0.1',
'Content-Type: application/json'
];
if (self::$mcpSessionId !== null && self::$mcpSessionId !== '') {
$headers[] = 'mcp-session-id: ' . self::$mcpSessionId;
}
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => self::$MCP_URL,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
if (curl_errno($ch)) {
curl_close($ch);
throw new Exception("cURL Error: " . curl_error($ch));
}
curl_close($ch);
$responseHeaders = substr($response, 0, $headerSize);
$responseBody = trim(substr($response, $headerSize));
preg_match_all('/^mcp-session-id:\s*(.+)$/im', $responseHeaders, $matches);
if (!empty($matches[1])) {
self::$mcpSessionId = trim($matches[1][0]);
}
return $responseBody;
}
public static function initialize()
{
return self::mcpCall("initialize", '{"protocolVersion": "2025-11-25"}');
}
public static function toolsCall($toolName, $arguments)
{
$params = json_encode(['name' => $toolName, 'arguments' => $arguments],
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
return self::mcpCall("tools/call", $params);
}
}
// 使用示例
McpClient::initialize();
McpClient::toolsCall("enterprise_query_state", [
"nsrsbh" => "91500112MAXXXX",
"username" => "1325580xxxx"
]);流式调用
use std::io::{self, BufRead};
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_TYPE};
const MCP_URL: &str = "https://mcp.fa-piao.com";
const MCP_TOKEN: &str = "YOUR_MCP_TOKEN";
const CLIENT_VERSION: &str = "1.0.1";
lazy_static::lazy_static! {
static ref MCP_SESSION_ID: Mutex<Option<String>> = Mutex::new(None);
}
static REQUEST_ID: AtomicI32 = AtomicI32::new(0);
fn get_session_id() -> Option<String> {
MCP_SESSION_ID.lock().unwrap().clone()
}
fn set_session_id(session_id: Option<String>) {
*MCP_SESSION_ID.lock().unwrap() = session_id;
}
fn next_request_id() -> i32 {
REQUEST_ID.fetch_add(1, Ordering::SeqCst) + 1
}
async fn read_stream_response(
body: reqwest::Response, content_type: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let is_sse = content_type.contains("text/event-stream");
if is_sse {
let mut response_body = String::new();
let reader = io::BufReader::new(body.bytes().await?);
for line in reader.lines() {
let line = line?;
if line.starts_with("data:") {
let data = line[5..].trim().to_string();
if data == "[DONE]" { break; }
response_body.push_str(&data);
}
}
Ok(response_body.trim().to_string())
} else {
let bytes = body.bytes().await?;
Ok(String::from_utf8(bytes.to_vec())?.trim().to_string())
}
}
pub async fn mcp_call_stream(
method: &str, params: Option<&str>,
) -> Result<String, Box<dyn std::error::Error>> {
let current_id = next_request_id();
let params_json = params.unwrap_or("{}");
let body = format!(
r#"{{"jsonrpc":"2.0","id":{},"method":"{}","params":{}}}"#,
current_id, method, params_json
);
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", MCP_TOKEN))?);
headers.insert(HeaderName::from_static("x-client-version"),
HeaderValue::from_static(CLIENT_VERSION));
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
headers.insert(ACCEPT,
HeaderValue::from_static("text/event-stream, application/json"));
if let Some(session_id) = get_session_id() {
headers.insert(HeaderName::from_static("mcp-session-id"),
HeaderValue::from_str(&session_id)?);
}
let client = reqwest::Client::new();
let response = client.post(MCP_URL).headers(headers).body(body).send().await?;
let response_session_id = response.headers()
.get(HeaderName::from_static("mcp-session-id"))
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
set_session_id(response_session_id);
let content_type = response.headers()
.get(CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
read_stream_response(response, content_type).await
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
mcp_call_stream("initialize", Some(r#"{"protocolVersion": "2025-11-25"}"#)).await?;
mcp_call_stream("notifications/initialized", Some("{}")).await?;
mcp_call_stream("tools/list", Some("{}")).await?;
mcp_call_stream("tools/call",
Some(r#"{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXXX","username":"1325580xxxx"}}"#)).await?;
Ok(())
}非流式调用
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
const MCP_URL: &str = "https://mcp.fa-piao.com";
const MCP_TOKEN: &str = "YOUR_MCP_TOKEN";
const CLIENT_VERSION: &str = "1.0.1";
lazy_static::lazy_static! {
static ref MCP_SESSION_ID: Mutex<Option<String>> = Mutex::new(None);
}
static REQUEST_ID: AtomicI32 = AtomicI32::new(0);
fn get_session_id() -> Option<String> {
MCP_SESSION_ID.lock().unwrap().clone()
}
fn set_session_id(session_id: Option<String>) {
*MCP_SESSION_ID.lock().unwrap() = session_id;
}
fn next_request_id() -> i32 {
REQUEST_ID.fetch_add(1, Ordering::SeqCst) + 1
}
pub async fn mcp_call(
method: &str, params: Option<&str>,
) -> Result<String, Box<dyn std::error::Error>> {
let current_id = next_request_id();
let params_json = params.unwrap_or("{}");
let body = format!(
r#"{{"jsonrpc":"2.0","id":{},"method":"{}","params":{}}}"#,
current_id, method, params_json
);
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", MCP_TOKEN))?);
headers.insert(HeaderName::from_static("x-client-version"),
HeaderValue::from_static(CLIENT_VERSION));
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
if let Some(session_id) = get_session_id() {
headers.insert(HeaderName::from_static("mcp-session-id"),
HeaderValue::from_str(&session_id)?);
}
let client = reqwest::Client::new();
let response = client.post(MCP_URL).headers(headers).body(body).send().await?;
let response_session_id = response.headers()
.get(HeaderName::from_static("mcp-session-id"))
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string());
set_session_id(response_session_id);
Ok(response.text().await?.trim().to_string())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
mcp_call("initialize", Some(r#"{"protocolVersion": "2025-11-25"}"#)).await?;
mcp_call("tools/call",
Some(r#"{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}"#)).await?;
Ok(())
}- 打开 Cherry Studio,进入设置页面
- 选择 「MCP」选项卡
- 点击 「添加」按钮
- 选择 「从 JSON 导入」,粘贴 MCP Server 配置 JSON
- 替换
YOUR_MCP_TOKEN为实际 Token,点击确定 - 打开 启用开关,即可在聊天窗口中使用
- 打开 Cursor,点击 【设置】→【Tools & MCP】
- 点击 【Add Custom MCP】,在
mcp.json中填入配置 - 替换
YOUR_MCP_TOKEN为实际 Token,保存 - 确认服务状态显示 【已连接】
- 按
CTRL/CMD + L打开 Agent 对话框,开始使用
- 打开 Trae,点击 【设置】→【MCP】→【手动添加】
- 填入 MCP Server 配置 JSON
- 替换
YOUR_MCP_TOKEN为实际 Token,点击确认 - 确认服务状态显示 【已连接】
- 选择 【Builder with MCP】 模式,开始使用
参考:TRAE MCP 文档
| 场景 | 说明 |
|---|---|
| 💬 对话式开票 | 对大模型说"帮我开一张发票",AI 自动完成全流程 |
| 📊 批量智能处理 | 上传 Excel 订单列表,AI 逐条解析并批量开票 |
| 🔍 智能发票查验 | AI 自动识别发票信息并调用查验接口 |
| 🔄 自动化财税流程 | 结合 RPA 和 AI,实现订单→开票→入账全流程自动化 |
| 🏗️ 企业 AI 中台集成 | 将发票 API 作为企业 AI 中台的标准工具 |
| 📱 AI 客服自动开票 | 智能客服机器人直接为用户开具发票 |
什么是 MCP (Model Context Protocol)?
MCP 是由 Anthropic 推出的开放标准协议,用于规范 AI 大模型与外部工具/API 之间的通信。fa-piao.com 的 MCP Server 实现了 Streamable HTTP 传输方式,支持实时流式响应。
支持哪些大模型平台?
支持所有兼容 MCP 协议的大模型平台,包括 Claude Desktop、Cursor、Cherry Studio、TRAE、Windsurf 等。只要平台支持 MCP 协议,即可直接使用。
MCP Server 的安全性如何保障?
- API Key 认证,每个请求需携带有效 Token
- 权限隔离,不同 API Key 可配置不同操作权限
- TLS 1.3 加密传输
- 请求频率限制,防止滥用
- 完整的操作审计日志
使用 MCP Server 和直接调用 API 有什么区别?
MCP Server 让您通过自然语言与大模型交互来完成开票,无需编写代码。两者底层调用的是同一套发票 API,功能和性能完全一致。MCP Server 更适合非技术用户和追求效率的场景。
MCP Server 是否包含在所有定价方案中?
是的,MCP Server 功能包含在所有定价方案中,包括 1 分钱试用版。
| 资源 | 链接 |
|---|---|
| 🌐 官网 | fa-piao.com |
| 📖 接口文档 | fa-piao.com/doc.html |
| 💰 价格方案 | fa-piao.com/pricing.html |
| 📦 SDK 资源 | fa-piao.com/sdk.html |
| 🔑 申请 Token | open.fa-piao.com |
| 💬 企业微信客服 | work.weixin.qq.com |
© 2025 fa-piao.com | 数电发票 API · 电子发票接口 · AI 开票 · MCP Server