About
电子发票MCP 数电发票MCP
README
让 AI 大模型成为您的开票助手 — 通过 MCP (Model Context Protocol) 协议,将电子发票/数电发票 API 接入 Claude、GPT、Cursor、Cherry Studio 等大模型平台,实现 AI 智能开票。
目录
什么是 MCP Server?
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 开关,完整请求/响应日志 |
快速开始
1. 获取 MCP Token
访问 open.fa-piao.com 注册并登录,申请 MCP Token。
2. 配置 MCP Server
在大模型客户端的配置文件中添加以下 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。
3. 开始使用
配置完成后,即可通过自然语言与大模型交互,例如:
- "查询纳税人 91500112MAXXXXX 的开票状态"
- "查验这张发票的真伪"
MCP Server 配置
| 配置项 | 值 | 说明 |
|---|---|---|
| 协议类型 | 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 协议,支持流式和非流式两种调用模式。
Python
流式调用
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))
Node.js
流式调用
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);
}
Java
流式调用
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\"}}");
}
}
Go
流式调用
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"}}`)
}
C#
流式调用
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
流式调用
<?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"
]);
Rust
流式调用
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
- 打开 Cherry Studio,进入设置页面
- 选择 「MCP」选项卡
- 点击 「添加」按钮
- 选择 「从 JSON 导入」,粘贴 MCP Server 配置 JSON
- 替换
YOUR_MCP_TOKEN为实际 Token,点击确定 - 打开 启用开关,即可在聊天窗口中使用
Cursor
- 打开 Cursor,点击 【设置】→【Tools & MCP】
- 点击 【Add Custom MCP】,在
mcp.json中填入配置 - 替换
YOUR_MCP_TOKEN为实际 Token,保存 - 确认服务状态显示 【已连接】
- 按
CTRL/CMD + L打开 Agent 对话框,开始使用
TRAE
- 打开 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
Installing Tax Invoice
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/fapiaoapi/tax-invoice-mcpFAQ
Is Tax Invoice MCP free?
Yes, Tax Invoice MCP is free — one-click install via Unyly at no cost.
Does Tax Invoice need an API key?
No, Tax Invoice runs without API keys or environment variables.
Is Tax Invoice hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Tax Invoice in Claude Desktop, Claude Code or Cursor?
Open Tax Invoice on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
Stripe
Payments, customers, subscriptions
by Stripemalamutemayhem/unclick-agent-native-endpoints
110+ tools for AI agents spanning social media, finance, gaming, music, AU-specific services, and utilities. Zero-config local tools plus platform connectors. n
by malamutemayhemwhiteknightonhorse/APIbase
Unified API hub for AI agents with 56+ tools across travel (Amadeus, Sabre), prediction markets (Polymarket), crypto, and weather. Pay-per-call via x402 micropa
by whiteknightonhorsetrackerfitness729-jpg/sitelauncher-mcp-server
Deploy live HTTPS websites in seconds. Instant subdomains ($1 USDC) or custom .xyz domains ($10 USDC) on Base chain. Templates for crypto tokens and AI agent pr
Compare Tax Invoice with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All finance MCPs
