MCP Server · AI大模型开票
通过 MCP (Model Context Protocol) 协议,让 Claude、GPT、Cursor 等大模型直接调用发票API,实现AI智能开票。使用前请先阅读 接口文档 了解完整API。
什么是 MCP Server?
Model Context Protocol — AI大模型与外部工具的标准通信协议
让 AI 成为您的开票助手
MCP(Model Context Protocol)是由 Anthropic 推出的开放标准协议,它定义了AI大模型如何安全、标准化地调用外部工具和API。fa-piao.com 率先实现了 MCP Server (Streamable HTTP),让任何支持 MCP 的大模型都能直接操作发票系统。
- Streamable HTTP 传输,实时流式响应
- 完整的 Tools 定义:开票/查询/红冲/查验
- 自然语言交互,无需编写代码
- 企业级 API Key 权限隔离
- 支持所有 MCP 兼容的大模型客户端
{
"mcpServers": {
"tax-invoice-mcp": {
"type": "streamableHttp",
"url": "https://mcp.fa-piao.com",
"headers": {
"Authorization": "Bearer YOUR_MCP_TOEKN",
"X-Client-Version": "1.0.1"
}
}
}
}
1. 申请 MCP Token
点击 open.fa-piao.com 注册并登录
申请 MCP Token
登录后,点击申请 MCP Token
MCP Token 申请成功,可以一键复制
2 多语言接入代码示例
选择编程语言和调用模式,查看对应的 MCP 客户端接入代码。所有示例均基于 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 # 设置为False可以关闭详细日志输出
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
masked_token = MCP_TOKEN[:4] + "***" + MCP_TOKEN[-4:]
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
resp = requests.post(
MCP_URL,
data=body,
headers=headers,
timeout=120
)
duration_ms = (time.time() - start_time) * 1000
status_code = resp.status_code
log_lines = []
if DEBUG:
log_lines.append(f"[{timestamp}] POST tool={method} id={current_id} token={masked_token} status={status_code} duration={duration_ms:.6f}ms")
response_session_id = resp.headers.get("mcp-session-id")
if response_session_id:
mcp_session_id = response_session_id
log_lines.append(f"session:{response_session_id}")
elif mcp_session_id:
log_lines.append(f"session:{mcp_session_id}")
log_lines.append("Request Headers:")
for key, value in headers.items():
display_value = "Bearer ***" if key == "Authorization" else value
log_lines.append(f" {key}: {display_value}")
log_lines.append("Request:")
log_lines.append(body)
content_type = resp.headers.get("Content-Type", "")
is_sse = "text/event-stream" in content_type
if is_sse:
response_body = parse_sse_response(resp)
else:
response_body = resp.text.strip()
if DEBUG:
log_lines.append(f"Response [status={status_code}]:")
log_lines.append(response_body)
print("\n".join(log_lines))
print()
return response_body
except Exception as e:
if DEBUG:
print(f"[异常] {method}: {e}")
return None
def parse_sse_response(resp):
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)
elif line.strip():
response_data.append(line.strip())
return "\n".join(response_data)
if __name__ == "__main__":
print("开始MCP调用测试...")
print(f"服务器地址: {MCP_URL}\n")
mcp_call_stream("initialize", {"protocolVersion": "2025-11-25"})
mcp_call_stream("tools/list", {})
mcp_call_stream("tools/call", {"name": "enterprise_query_state", "arguments": {"nsrsbh": "91500112MAXXXXX", "username": "1325580xxxx"}})
print("结束MCP调用测试...")
import requests
import json
import time
from datetime import datetime
MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"
# 全局变量控制调试模式
DEBUG = True # 设置为False可以关闭详细日志输出
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
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}",
"X-Client-Version": "1.0.1",
"Content-Type": "application/json"
}
if mcp_session_id:
headers["mcp-session-id"] = mcp_session_id
masked_token = MCP_TOKEN[:4] + "***" + MCP_TOKEN[-4:]
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
resp = requests.post(
MCP_URL,
data=body,
headers=headers,
timeout=120
)
duration_ms = (time.time() - start_time) * 1000
status_code = resp.status_code
log_lines = []
if DEBUG:
log_lines.append(f"[{timestamp}] POST tool={method} id={current_id} token={masked_token} status={status_code} duration={duration_ms:.6f}ms")
response_session_id = resp.headers.get("mcp-session-id")
if response_session_id:
mcp_session_id = response_session_id
log_lines.append(f"session:{response_session_id}")
elif mcp_session_id:
log_lines.append(f"session:{mcp_session_id}")
log_lines.append("Request Headers:")
for key, value in headers.items():
display_value = "Bearer ***" if key == "Authorization" else value
log_lines.append(f" {key}: {display_value}")
log_lines.append("Request:")
log_lines.append(body)
response_body = resp.text.strip()
if DEBUG:
log_lines.append(f"Response [status={status_code}]:")
log_lines.append(response_body)
print("\n".join(log_lines))
print()
return response_body
except Exception as e:
if DEBUG:
print(f"[异常] {method}: {e}")
return None
if __name__ == "__main__":
print("开始MCP调用测试...")
print(f"服务器地址: {MCP_URL}\n")
mcp_call("initialize", {"protocolVersion": "2025-11-25"})
# mcp_call("tools/list", None)
mcp_call("tools/call", {"name": "enterprise_query_state", "arguments": {"nsrsbh": "91500112MAXXXXX", "username": "1325580xxxx"}})
print("结束MCP调用测试...")
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 formatLogTimestamp(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
function maskToken(token: string): string {
return token.substring(0, 4) + '***' + token.substring(token.length - 4);
}
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);
} else if (line.trim()) {
responseData.push(line.trim());
}
}
return responseData.join('\n');
}
export async function mcpCallStream(method: string, params?: any): Promise {
const startTime = Date.now();
const currentId = getNextRequestId();
const payload = {
jsonrpc: '2.0',
id: currentId,
method: method,
params: params !== undefined && params !== null ? params : {}
};
const body = JSON.stringify(payload);
const headers: Record = {
'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;
}
const maskedToken = maskToken(MCP_TOKEN);
const timestamp = formatLogTimestamp(new Date());
try {
const response: AxiosResponse = await axios.post(MCP_URL, body, {
headers: headers,
timeout: 120000,
validateStatus: () => true,
responseType: 'text',
transformResponse: [(data) => data]
});
const durationMs = (Date.now() - startTime);
const statusCode = response.status;
const logLines: string[] = [];
if (DEBUG) {
logLines.push(`[${timestamp}] POST tool=${method} id=${currentId} token=${maskedToken} status=${statusCode} duration=${durationMs.toFixed(6)}ms`);
const responseSessionId = response.headers['mcp-session-id'];
if (responseSessionId && !Array.isArray(responseSessionId)) {
mcpSessionId = responseSessionId;
logLines.push(`session:${responseSessionId}`);
} else if (mcpSessionId) {
logLines.push(`session:${mcpSessionId}`);
}
logLines.push('Request Headers:');
Object.entries(headers).forEach(([key, value]) => {
const displayValue = key === 'Authorization' ? 'Bearer ***' : value;
logLines.push(` ${key}: ${displayValue}`);
});
logLines.push('Request:');
logLines.push(body);
}
const contentType = response.headers['content-type'] || '';
const isSse = contentType.includes('text/event-stream');
let responseBody: string;
if (isSse) {
responseBody = parseSseResponse(response.data);
} else {
responseBody = response.data.trim();
}
if (DEBUG) {
logLines.push(`Response [status=${statusCode}]:`);
logLines.push(responseBody);
console.log(logLines.join('\n'));
console.log();
}
return responseBody;
} catch (error) {
if (DEBUG) {
console.error(`[异常] ${method}:`, error instanceof Error ? error.message : error);
}
return null;
}
}
async function main() {
console.log('开始MCP调用测试...');
console.log(`服务器地址: ${MCP_URL}\n`);
await mcpCallStream('initialize', { protocolVersion: '2025-11-25' });
await mcpCallStream('tools/list', {});
await mcpCallStream('tools/call', {
name: 'enterprise_query_state',
arguments: {
nsrsbh: '91500112MAXXXXX',
username: '1325580xxxx'
}
});
console.log('结束MCP调用测试...');
}
if (require.main === module) {
main().catch(console.error);
}
import axios, { AxiosResponse } from 'axios';
const DEBUG = true;
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;
}
function formatLogTimestamp(date: Date): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
function maskToken(token: string): string {
return token.substring(0, 4) + '***' + token.substring(token.length - 4);
}
export async function mcpCall(method: string, params?: string): Promise {
const startTime = Date.now();
const currentId = nextRequestId();
const body = JSON.stringify({
jsonrpc: '2.0',
id: currentId,
method: method,
params: params ? JSON.parse(params) : {}
});
const headers: Record = {
'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 duration = Date.now() - startTime;
const durationMs = duration / 1000.0;
if (DEBUG) {
const maskedToken = maskToken(MCP_TOKEN);
const timestamp = formatLogTimestamp(new Date());
const statusCode = response.status;
const logBuilder: string[] = [];
logBuilder.push(`[${timestamp}] POST tool=${method} id=${currentId} token=${maskedToken} status=${statusCode} duration=${durationMs.toFixed(6)}ms`);
const responseSessionId = response.headers['mcp-session-id'];
if (responseSessionId && !Array.isArray(responseSessionId)) {
mcpSessionId = responseSessionId;
logBuilder.push(`session:${responseSessionId}`);
} else if (mcpSessionId) {
logBuilder.push(`session:${mcpSessionId}`);
}
logBuilder.push('Request Headers:');
Object.entries(headers).forEach(([key, value]) => {
let displayValue = value;
if (key.toLowerCase() === 'authorization') {
displayValue = 'Bearer ***';
}
logBuilder.push(` ${key}: ${displayValue}`);
});
logBuilder.push('Request:');
logBuilder.push(body);
const responseData = typeof response.data === 'string' ? response.data : JSON.stringify(response.data, null, 2);
logBuilder.push(`Response [status=${statusCode}]:`);
logBuilder.push(responseData);
console.log(logBuilder.join('\n'));
} else {
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() {
try {
console.log('=== MCP Call Test ===\n');
const initResult = await mcpCall('initialize', '{"protocolVersion": "2025-11-25"}');
console.log('\n--- Initialize Result ---');
console.log(initResult);
console.log('\n--- Calling Tool ---');
const callResult = await mcpCall('tools/call', '{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}');
console.log('\n--- Tool Call Result ---');
console.log(callResult);
console.log('\n=== Test Complete ===');
} catch (error) {
console.error('Error:', error instanceof Error ? error.message : error);
}
}
if (require.main === module) {
main().catch(console.error);
}
import java.net.URI;
import java.net.http.*;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class McpStreamClient {
// ====================== DEBUG 开关:true=打印日志,false=关闭日志 ======================
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 mcpSessionId = new AtomicReference<>(null);
private static final AtomicInteger requestId = new AtomicInteger(0);
private static final DateTimeFormatter LOG_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static int nextRequestId() {
return requestId.incrementAndGet();
}
public static String mcpCallStream(String method, String params) throws Exception {
long startTime = System.nanoTime();
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);
}
HttpRequest req = requestBuilder.build();
Map> requestHeaders = req.headers().map();
HttpClient client = HttpClient.newHttpClient();
HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofInputStream());
long duration = (System.nanoTime() - startTime) / 1_000_000;
double durationMs = duration / 1000.0;
String responseBody = readStreamResponse(resp.body(), resp.headers());
// ====================== 只有 DEBUG=true 才打印日志 ======================
if (DEBUG) {
String maskedToken = MCP_TOKEN.substring(0, 4) + "***" + MCP_TOKEN.substring(MCP_TOKEN.length() - 4);
LocalDateTime now = LocalDateTime.now();
String timestamp = now.format(LOG_FORMATTER);
int statusCode = resp.statusCode();
StringBuilder logBuilder = new StringBuilder();
logBuilder.append(String.format("[%s] POST tool=%s id=%d token=%s status=%d duration=%.6fms%n",
timestamp, method, currentId, maskedToken, statusCode, durationMs));
String responseSessionId = resp.headers().firstValue("mcp-session-id").orElse(null);
if (responseSessionId != null) {
mcpSessionId.set(responseSessionId);
logBuilder.append(String.format("session:%s%n", responseSessionId));
} else if (sessionId != null) {
logBuilder.append(String.format("session:%s%n", sessionId));
}
logBuilder.append("Request Headers:%n");
for (Map.Entry> entry : requestHeaders.entrySet()) {
String headerName = entry.getKey();
List headerValues = entry.getValue();
if (!headerValues.isEmpty()) {
String value = headerValues.get(0);
if ("Authorization".equalsIgnoreCase(headerName)) {
value = "Bearer ***";
}
logBuilder.append(String.format(" %s: %s%n", headerName, value));
}
}
logBuilder.append("Request:%n");
logBuilder.append(body).append("\n");
logBuilder.append(String.format("Response [status=%d]:%n", statusCode));
logBuilder.append(responseBody);
// 打印日志
System.out.println(logBuilder);
}
return responseBody;
}
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 if (!line.isEmpty()) {
responseBody.append(line);
}
}
} 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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class McpClient {
private 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 mcpSessionId = new AtomicReference<>(null);
private static final AtomicInteger requestId = new AtomicInteger(0);
private static final DateTimeFormatter LOG_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static int nextRequestId() {
return requestId.incrementAndGet();
}
public static String mcpCall(String method, String params) throws Exception {
long startTime = System.nanoTime();
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);
}
HttpRequest req = requestBuilder.build();
Map> requestHeaders = req.headers().map();
HttpClient client = HttpClient.newHttpClient();
HttpResponse resp = client.send(req, HttpResponse.BodyHandlers.ofString());
long duration = (System.nanoTime() - startTime) / 1_000_000;
double durationMs = duration / 1000.0;
if (DEBUG) {
String maskedToken = MCP_TOKEN.substring(0, 4) + "***" + MCP_TOKEN.substring(MCP_TOKEN.length() - 4);
LocalDateTime now = LocalDateTime.now();
String timestamp = now.format(LOG_FORMATTER);
int statusCode = resp.statusCode();
StringBuilder logBuilder = new StringBuilder();
logBuilder.append(String.format("[%s] POST tool=%s id=%d token=%s status=%d duration=%.6fms\n",
timestamp, method, currentId, maskedToken, statusCode, durationMs));
String responseSessionId = resp.headers().firstValue("mcp-session-id").orElse(null);
if (responseSessionId != null) {
mcpSessionId.set(responseSessionId);
logBuilder.append(String.format("session:%s\n", responseSessionId));
} else if (sessionId != null) {
logBuilder.append(String.format("session:%s\n", sessionId));
}
logBuilder.append("Request Headers:\n");
for (Map.Entry> entry : requestHeaders.entrySet()) {
String headerName = entry.getKey();
List headerValues = entry.getValue();
if (!headerValues.isEmpty()) {
String value = headerValues.get(0);
if ("Authorization".equalsIgnoreCase(headerName)) {
value = "Bearer ***";
}
logBuilder.append(String.format(" %s: %s\n", headerName, value));
}
}
logBuilder.append("Request:\n");
logBuilder.append(body).append("\n");
logBuilder.append(String.format("Response [status=%d]:\n", statusCode));
logBuilder.append(resp.body());
System.out.println(logBuilder.toString());
}
return resp.body();
}
public static void main(String[] args) throws Exception {
mcpCall("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
// mcpCall("notifications/initialized", "{}");
// mcpCall("tools/list", null);
mcpCall("tools/call", "{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXX\",\"username\":\"1325580xxxx\"}}");
}
}
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"net/http"
"strings"
"sync/atomic"
"time"
)
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) {
startTime := time.Now()
currentId := nextRequestId()
body := fmt.Sprintf(`{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}`,
currentId, method, "{}")
if params != "" {
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()
duration := time.Since(startTime).Milliseconds()
durationMs := float64(duration) / 1000.0
responseBody, err := readStreamResponse(resp.Body, resp.Header.Get("Content-Type"))
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
if DEBUG {
maskedToken := MCP_TOKEN[:4] + "***" + MCP_TOKEN[len(MCP_TOKEN)-4:]
timestamp := time.Now().Format("2006-01-02 15:04:05")
statusCode := resp.StatusCode
var logBuilder strings.Builder
logBuilder.WriteString(fmt.Sprintf("[%s] POST tool=%s id=%d token=%s status=%d duration=%.6fms\n",
timestamp, method, currentId, maskedToken, statusCode, durationMs))
responseSessionId := resp.Header.Get("mcp-session-id")
if responseSessionId != "" {
mcpSessionId.Store(responseSessionId)
logBuilder.WriteString(fmt.Sprintf("session:%s\n", responseSessionId))
} else if sessionId != "" {
logBuilder.WriteString(fmt.Sprintf("session:%s\n", sessionId))
}
logBuilder.WriteString("Request Headers:\n")
for key, values := range req.Header {
if len(values) > 0 {
value := values[0]
if strings.EqualFold(key, "Authorization") {
value = "Bearer ***"
}
logBuilder.WriteString(fmt.Sprintf(" %s: %s\n", key, value))
}
}
logBuilder.WriteString("Request:\n")
logBuilder.WriteString(body + "\n")
logBuilder.WriteString(fmt.Sprintf("Response [status=%d]:\n", statusCode))
logBuilder.WriteString(responseBody)
fmt.Println(logBuilder.String())
}
return responseBody, nil
}
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 if line != "" {
responseBody.WriteString(line)
}
}
} else {
all, err := io.ReadAll(reader)
if err != nil {
return "", err
}
responseBody.Write(all)
}
return strings.TrimSpace(responseBody.String()), nil
}
func main() {
// 示例调用
result, err := mcpCallStream("initialize", `{"protocolVersion": "2025-11-25"}`)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Initialize result: %s\n", result)
result, err = mcpCallStream("notifications/initialized", "{}")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Initialized result: %s\n", result)
result, err = mcpCallStream("tools/list", "{}")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Tools list result: %s\n", result)
result, err = mcpCallStream("tools/call", `{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXXX","username":"1325580xxxx"}}`)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Tools call result: %s\n", result)
}
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
"sync/atomic"
"time"
)
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 mcpCall(method string, params string) (string, error) {
startTime := time.Now()
currentId := nextRequestId()
body := fmt.Sprintf(`{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}`,
currentId, method, "{}")
if params != "" {
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()
duration := time.Since(startTime).Milliseconds()
durationMs := float64(duration) / 1000.0
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
responseBody := string(respBody)
if DEBUG {
maskedToken := MCP_TOKEN[:4] + "***" + MCP_TOKEN[len(MCP_TOKEN)-4:]
timestamp := time.Now().Format("2006-01-02 15:04:05")
statusCode := resp.StatusCode
var logBuilder strings.Builder
logBuilder.WriteString(fmt.Sprintf("[%s] POST tool=%s id=%d token=%s status=%d duration=%.6fms\n",
timestamp, method, currentId, maskedToken, statusCode, durationMs))
responseSessionId := resp.Header.Get("mcp-session-id")
if responseSessionId != "" {
mcpSessionId.Store(responseSessionId)
logBuilder.WriteString(fmt.Sprintf("session:%s\n", responseSessionId))
} else if sessionId != "" {
logBuilder.WriteString(fmt.Sprintf("session:%s\n", sessionId))
}
logBuilder.WriteString("Request Headers:\n")
for key, values := range req.Header {
if len(values) > 0 {
value := values[0]
if strings.EqualFold(key, "Authorization") {
value = "Bearer ***"
}
logBuilder.WriteString(fmt.Sprintf(" %s: %s\n", key, value))
}
}
logBuilder.WriteString("Request:\n")
logBuilder.WriteString(body + "\n")
logBuilder.WriteString(fmt.Sprintf("Response [status=%d]:\n", statusCode))
logBuilder.WriteString(responseBody)
fmt.Println(logBuilder.String())
}
return responseBody, nil
}
func main() {
result, err := mcpCall("initialize", `{"protocolVersion": "2025-11-25"}`)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Initialize result: %s\n", result)
result, err = mcpCall("tools/call", `{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}`)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Tools call result: %s\n", result)
}
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
namespace Tax.Invoice.Example
{
public class McpStreamClient
{
// ====================== DEBUG 开关:true=打印日志,false=关闭日志 ======================
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 McpCallStream(string method, string? paramsJson = null)
{
var startTime = Stopwatch.GetTimestamp();
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("Content-Type", "application/json");
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");
var requestHeaders = new Dictionary>();
foreach (var header in requestBuilder.Headers)
{
requestHeaders[header.Key] = header.Value.ToList();
}
using var client = new HttpClient();
var response = await client.SendAsync(requestBuilder);
var duration = (Stopwatch.GetTimestamp() - startTime) * 1000.0 / Stopwatch.Frequency;
double durationMs = duration;
string responseBody = await ReadStreamResponse(response.Content.ReadAsStreamAsync().Result, response.Headers);
// ====================== 只有 DEBUG=true 才打印日志 ======================
if (DEBUG)
{
string maskedToken = MCP_TOKEN.Substring(0, 4) + "***" + MCP_TOKEN.Substring(MCP_TOKEN.Length - 4);
var now = DateTime.Now;
string timestamp = now.ToString("yyyy-MM-dd HH:mm:ss");
int statusCode = (int)response.StatusCode;
StringBuilder logBuilder = new StringBuilder();
logBuilder.AppendFormat("[{0}] POST tool={1} id={2} token={3} status={4} duration={5:F6}ms\n",
timestamp, method, currentId, maskedToken, statusCode, durationMs);
string? responseSessionId = null;
if (response.Headers.TryGetValues("mcp-session-id", out var sessionValues))
{
responseSessionId = sessionValues.FirstOrDefault();
}
if (!string.IsNullOrEmpty(responseSessionId))
{
mcpSessionId = responseSessionId;
logBuilder.AppendFormat("session:{0}\n", responseSessionId);
}
else if (!string.IsNullOrEmpty(mcpSessionId))
{
logBuilder.AppendFormat("session:{0}\n", mcpSessionId);
}
logBuilder.Append("Request Headers:\n");
foreach (var entry in requestHeaders)
{
string headerName = entry.Key;
var headerValues = entry.Value;
if (headerValues.Count > 0)
{
string value = headerValues[0];
if ("Authorization".Equals(headerName, StringComparison.OrdinalIgnoreCase))
{
value = "Bearer ***";
}
logBuilder.AppendFormat(" {0}: {1}\n", headerName, value);
}
}
logBuilder.Append("Request:\n");
logBuilder.Append(body).Append("\n");
logBuilder.AppendFormat("Response [status={0}]:\n", statusCode);
logBuilder.Append(responseBody);
// 打印日志
Console.WriteLine(logBuilder.ToString());
}
return responseBody.Trim();
}
private static async Task ReadStreamResponse(Stream inputStream, System.Net.Http.Headers.HttpResponseHeaders respHeaders)
{
using var reader = new StreamReader(inputStream, Encoding.UTF8);
StringBuilder responseBody = new StringBuilder();
string? line;
string contentType = "";
if (respHeaders.TryGetValues("Content-Type", out var contentTypes))
{
contentType = contentTypes.FirstOrDefault() ?? "";
}
bool isSSE = contentType.Contains("text/event-stream");
if (isSSE)
{
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 if (!string.IsNullOrEmpty(line))
{
responseBody.Append(line);
}
}
}
else
{
int ch;
while ((ch = await reader.ReadAsync()) != -1)
{
responseBody.Append((char)ch);
}
}
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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace Tax.Invoice.Example
{
public class McpClient
{
private 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 McpCall(string method, string? paramsJson = null)
{
var startTime = Stopwatch.GetTimestamp();
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("Content-Type", "application/json");
if (!string.IsNullOrEmpty(mcpSessionId))
{
requestBuilder.Headers.TryAddWithoutValidation("mcp-session-id", mcpSessionId);
}
requestBuilder.Content = new StringContent(body, Encoding.UTF8, "application/json");
var requestHeaders = new Dictionary>();
foreach (var header in requestBuilder.Headers)
{
requestHeaders[header.Key] = header.Value.ToList();
}
using var client = new HttpClient();
var response = await client.SendAsync(requestBuilder);
string responseBody = await response.Content.ReadAsStringAsync();
var duration = (Stopwatch.GetTimestamp() - startTime) * 1000.0 / Stopwatch.Frequency;
double durationMs = duration;
if (DEBUG)
{
string maskedToken = MCP_TOKEN.Substring(0, 4) + "***" + MCP_TOKEN.Substring(MCP_TOKEN.Length - 4);
var now = DateTime.Now;
string timestamp = now.ToString("yyyy-MM-dd HH:mm:ss");
int statusCode = (int)response.StatusCode;
StringBuilder logBuilder = new StringBuilder();
logBuilder.AppendFormat("[{0}] POST tool={1} id={2} token={3} status={4} duration={5:F6}ms\n",
timestamp, method, currentId, maskedToken, statusCode, durationMs);
string? responseSessionId = null;
if (response.Headers.TryGetValues("mcp-session-id", out var sessionValues))
{
responseSessionId = sessionValues.FirstOrDefault();
}
if (!string.IsNullOrEmpty(responseSessionId))
{
mcpSessionId = responseSessionId;
logBuilder.AppendFormat("session:{0}\n", responseSessionId);
}
else if (!string.IsNullOrEmpty(mcpSessionId))
{
logBuilder.AppendFormat("session:{0}\n", mcpSessionId);
}
logBuilder.Append("Request Headers:\n");
foreach (var entry in requestHeaders)
{
string headerName = entry.Key;
var headerValues = entry.Value;
if (headerValues.Count > 0)
{
string value = headerValues[0];
if ("Authorization".Equals(headerName, StringComparison.OrdinalIgnoreCase))
{
value = "Bearer ***";
}
logBuilder.AppendFormat(" {0}: {1}\n", headerName, value);
}
}
logBuilder.Append("Request:\n");
logBuilder.Append(body).Append("\n");
logBuilder.AppendFormat("Response [status={0}]:\n", statusCode);
logBuilder.Append(responseBody);
Console.WriteLine(logBuilder.ToString());
}
return responseBody;
}
public static async Task Main(string[] args)
{
await McpCall("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
// await McpCall("notifications/initialized", "{}");
// await McpCall("tools/list", null);
await McpCall("tools/call", "{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXX\",\"username\":\"1325580xxxx\"}}");
}
}
}
'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 => false,
CURLOPT_HEADER => true,
CURLOPT_WRITEFUNCTION => function($curl, $data) use (&$responseBody, &$headerReceived) {
$length = strlen($data);
if (!$headerReceived && strpos($data, "\r\n\r\n") !== false) {
$parts = explode("\r\n\r\n", $data, 2);
$headerPart = $parts[0];
$bodyPart = $parts[1] ?? '';
preg_match_all('/^([^:]+):\s*(.+)$/m', $headerPart, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$headerName = trim($match[1]);
$headerValue = trim($match[2]);
if (strtolower($headerName) === 'mcp-session-id') {
self::$mcpSessionId = $headerValue;
}
}
$headerReceived = true;
if (!empty($bodyPart)) {
$responseBody .= $bodyPart;
}
} elseif ($headerReceived) {
$responseBody .= $data;
} else {
if (strpos($data, "\r\n\r\n") === false) {
$responseBody .= $data;
}
}
return $length;
},
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
]);
$responseBody = '';
$headerReceived = false;
$result = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
$duration = (microtime(true) - $startTime) * 1000;
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: " . $error);
}
curl_close($ch);
$responseBody = trim($responseBody);
// ====================== 只有 DEBUG=true 才打印日志 ======================
if (self::DEBUG) {
$maskedToken = substr(self::$MCP_TOKEN, 0, 4) . '***' . substr(self::$MCP_TOKEN, -4);
$timestamp = date('Y-m-d H:i:s');
$logBuilder = "";
$logBuilder .= sprintf("[%s] POST tool=%s id=%d token=%s status=%d duration=%.6fms\n",
$timestamp, $method, $currentId, $maskedToken, $httpCode, $duration);
if (self::$mcpSessionId !== null) {
$logBuilder .= sprintf("session:%s\n", self::$mcpSessionId);
}
$logBuilder .= "Request Headers:\n";
foreach ($headers as $header) {
if (stripos($header, 'Authorization:') === 0) {
$logBuilder .= " Authorization: Bearer ***\n";
} else {
$logBuilder .= " " . $header . "\n";
}
}
$logBuilder .= "Request:\n";
$logBuilder .= $body . "\n";
$logBuilder .= sprintf("Response [status=%d]:\n", $httpCode);
$logBuilder .= $responseBody;
echo $logBuilder;
}
return $responseBody;
}
public static function initialize()
{
return self::mcpCallStream("initialize", '{"protocolVersion": "2025-11-25"}');
}
public static function notificationsInitialized()
{
return self::mcpCallStream("notifications/initialized", '{}');
}
public static function toolsList()
{
return self::mcpCallStream("tools/list", '{}');
}
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);
}
}
// 使用示例
if (php_sapi_name() === 'cli') {
try {
McpStreamClient::initialize();
McpStreamClient::notificationsInitialized();
McpStreamClient::toolsList();
McpStreamClient::toolsCall("enterprise_query_state", [
"nsrsbh" => "91500112MAXXXXX",
"username" => "1325580xxxx"
]);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
}
'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_SSL_VERIFYHOST => 2,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$duration = (microtime(true) - $startTime) * 1000;
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("cURL Error: " . $error);
}
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]);
}
// ====================== 只有 DEBUG=true 才打印日志 ======================
if (self::DEBUG) {
$maskedToken = substr(self::$MCP_TOKEN, 0, 4) . '***' . substr(self::$MCP_TOKEN, -4);
$timestamp = date('Y-m-d H:i:s');
$logBuilder = "";
$logBuilder .= sprintf("[%s] POST tool=%s id=%d token=%s status=%d duration=%.6fms\n",
$timestamp, $method, $currentId, $maskedToken, $httpCode, $duration);
if (self::$mcpSessionId !== null) {
$logBuilder .= sprintf("session:%s\n", self::$mcpSessionId);
}
$logBuilder .= "Request Headers:\n";
foreach ($headers as $header) {
if (stripos($header, 'Authorization:') === 0) {
$logBuilder .= " Authorization: Bearer ***\n";
} else {
$logBuilder .= " " . $header . "\n";
}
}
$logBuilder .= "Request:\n";
$logBuilder .= $body . "\n";
$logBuilder .= sprintf("Response [status=%d]:\n", $httpCode);
$logBuilder .= $responseBody . "\n";
echo $logBuilder;
}
return $responseBody;
}
public static function initialize()
{
return self::mcpCall("initialize", '{"protocolVersion": "2025-11-25"}');
}
public static function notificationsInitialized()
{
return self::mcpCall("notifications/initialized", '{}');
}
public static function toolsList()
{
return self::mcpCall("tools/list", null);
}
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);
}
}
// 使用示例
if (php_sapi_name() === 'cli') {
try {
McpClient::initialize();
McpClient::notificationsInitialized();
McpClient::toolsList();
McpClient::toolsCall("enterprise_query_state", [
"nsrsbh" => "91500112MAXXXX",
"username" => "1325580xxxx"
]);
} catch (Exception $e) {
echo "Error: " . $e->getMessage() . "\n";
}
}
use std::io::{self, BufRead, Read};
use std::sync::atomic::{AtomicI32, AtomicUsize, Ordering};
use std::time::Instant;
use chrono::Local;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_TYPE};
// ====================== DEBUG 开关:true=打印日志,false=关闭日志 ======================
const DEBUG: bool = true;
const MCP_URL: &str = "https://mcp.fa-piao.com";
const MCP_TOKEN: &str = "YOUR_MCP_TOKEN";
const CLIENT_VERSION: &str = "1.0.1";
static MCP_SESSION_ID: AtomicUsize = AtomicUsize::new(0);
static REQUEST_ID: AtomicI32 = AtomicI32::new(0);
fn get_session_id() -> Option {
let id = MCP_SESSION_ID.load(Ordering::SeqCst);
if id == 0 {
None
} else {
Some(id.to_string())
}
}
fn set_session_id(session_id: &str) {
if let Ok(id) = session_id.parse::() {
MCP_SESSION_ID.store(id, Ordering::SeqCst);
}
}
fn next_request_id() -> i32 {
REQUEST_ID.fetch_add(1, Ordering::SeqCst) + 1
}
fn mask_token(token: &str) -> String {
if token.len() >= 8 {
format!("{}***{}", &token[..4], &token[token.len() - 4..])
} else {
"***".to_string()
}
}
async fn read_stream_response(
mut body: reqwest::Response,
content_type: &str,
) -> Result> {
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);
} else if !line.is_empty() {
response_body.push_str(&line);
}
}
Ok(response_body.trim().to_string())
} else {
let bytes = body.bytes().await?;
let text = String::from_utf8(bytes.to_vec())?;
Ok(text.trim().to_string())
}
}
pub async fn mcp_call_stream(method: &str, params: Option<&str>) -> Result> {
let start_time = Instant::now();
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 request_builder = client.post(MCP_URL).headers(headers.clone()).body(body.clone());
let response = request_builder.send().await?;
let status = response.status();
let status_code = status.as_u16();
let response_headers = response.headers().clone();
let content_type = response_headers
.get(CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.unwrap_or("");
let response_session_id = response_headers
.get(HeaderName::from_static("mcp-session-id"))
.and_then(|v| v.to_str().ok());
if let Some(session_id) = response_session_id {
set_session_id(session_id);
}
let duration = start_time.elapsed();
let duration_ms = duration.as_secs_f64() * 1000.0;
let response_body = read_stream_response(response, content_type).await?;
// ====================== 只有 DEBUG=true 才打印日志 ======================
if DEBUG {
let masked_token = mask_token(MCP_TOKEN);
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S");
println!(
"[{}] POST tool={} id={} token={} status={} duration={:.6}ms",
timestamp, method, current_id, masked_token, status_code, duration_ms
);
if let Some(session_id) = get_session_id() {
println!("session:{}", session_id);
}
println!("Request Headers:");
for (name, value) in headers.iter() {
let header_name = name.as_str();
let header_value = value.to_str().unwrap_or("");
if header_name.eq_ignore_ascii_case("authorization") {
println!(" {}: Bearer ***", header_name);
} else {
println!(" {}: {}", header_name, header_value);
}
}
println!("Request:");
println!("{}", body);
println!("Response [status={}]:", status_code);
print!("{}", response_body);
}
Ok(response_body)
}
pub async fn initialize() -> Result> {
mcp_call_stream("initialize", Some(r#"{"protocolVersion": "2025-11-25"}"#)).await
}
pub async fn notifications_initialized() -> Result> {
mcp_call_stream("notifications/initialized", Some("{}")).await
}
pub async fn tools_list() -> Result> {
mcp_call_stream("tools/list", Some("{}")).await
}
pub async fn tools_call(tool_name: &str, arguments: &serde_json::Value) -> Result> {
let params = serde_json::json!({
"name": tool_name,
"arguments": arguments
});
let params_str = serde_json::to_string(¶ms)?;
mcp_call_stream("tools/call", Some(¶ms_str)).await
}
#[tokio::main]
async fn main() -> Result<(), Box> {
initialize().await?;
notifications_initialized().await?;
tools_list().await?;
let args = serde_json::json!({
"nsrsbh": "91500112MAXXXXX",
"username": "1325580xxxx"
});
tools_call("enterprise_query_state", &args).await?;
Ok(())
}
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde_json = "1.0"
chrono = "0.4"
// 直接调用
let response = mcp_call_stream("tools/call", Some(r#"{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXXX"}}"#)).await?;
// 或使用便捷函数
initialize().await?;
let args = serde_json::json!({
"nsrsbh": "91500112MAXXXXX",
"username": "1325580xxxx"
});
tools_call("enterprise_query_state", &args).await?;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use std::time::Instant;
use chrono::Local;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
// ====================== DEBUG 开关:true=打印日志,false=关闭日志 ======================
const DEBUG: bool = true;
const MCP_URL: &str = "https://mcp.fa-piao.com";
const MCP_TOKEN: &str = "YOUR_MCP_TOKEN";
const CLIENT_VERSION: &str = "1.0.1";
static REQUEST_ID: AtomicI32 = AtomicI32::new(0);
lazy_static::lazy_static! {
static ref MCP_SESSION_ID: Mutex
3. 各平台接入教程
Cherry Studio
参考 Cherry Studio 官方文档:docs.cherry-ai.com/advanced-basic/mcp
1、打开 Cherry Studio,进入设置页面
2、选择「MCP」选项卡
3、点击「添加」按钮
4、在弹出的下拉框中,选择「从 JSON 导入」
从上面复制 JSON 粘贴进去,一定记得替换 "YOUR_MCP_TOKEN",然后点击「确定」按钮
添加完成后,请打开启用开关
配置完成。现在可以在聊天窗口中使用 MCP 功能。
Cursor
参考 Cursor 官方文档:cursor.com/cn/docs/context/mcp
打开 Cursor,点击顶部菜单栏【设置】→【Tools & MCP】,在 Installed MCP Servers 中点击【Add Custom MCP】
在打开的 mcp.json 文件中填入从上面复制的 JSON 内容,一定记得替换 YOUR_MCP_TOKEN 为实际 MCP Token,点击【关闭】并选择【保存】
回到设置页面中,此时应显示可用的 MCP 工具,服务状态应显示为【已连接】
按下 CTRL/CMD + L 打开右侧 Agent 对话框,接下来就可以直接在对话框中输入需求,让 AI 为我们调用工具了
TRAE
参考 TRAE 官方文档:docs.trae.cn/ide/model-context-protocol
打开 Trae,点击【设置】→【MCP】→【手动添加】进行添加
在打开的手动配置页面中填入从上面复制的 JSON 内容,一定记得替换 YOUR_MCP_TOKEN 为实际 MCP Token,点击【确认】
回到 MCP 页面中,此时应显示可用的 MCP 工具,服务状态应显示为【已连接】
回到对话框中,选择【Builder with MCP】
接下来就可以直接在对话框中输入需求,让 AI 为我们调用工具了
AI 开票的典型应用
大模型 + 发票API,开启智能财税新时代
对话式开票
直接对大模型说"帮我开一张发票,购买方是XX公司,金额1000元",AI自动完成开票全流程并返回结果。
批量智能处理
上传Excel订单列表,AI自动逐条解析并批量开具发票,大幅提升财务工作效率。
智能发票查验
AI自动识别发票信息并调用查验接口,快速验证发票真伪,防止假发票入账。
自动化财税流程
结合RPA和AI,实现从订单到开票到入账的全流程自动化,减少人工干预。
企业AI中台集成
将发票API作为企业AI中台的标准工具,供内部多个AI应用统一调用。
AI客服自动开票
智能客服机器人直接为用户开具发票,提升客户服务体验和响应速度。
MCP Server 常见疑问
MCP 是由 Anthropic 推出的开放标准协议,用于规范AI大模型与外部工具/API之间的通信。它定义了统一的接口格式,让任何支持MCP的大模型都能标准化地调用外部服务。fa-piao.com 的 MCP Server 实现了 Streamable HTTP 传输方式,支持实时流式响应。
目前支持所有兼容 MCP 协议的大模型平台,包括:Claude Desktop(Anthropic官方客户端)、Cursor/Windsurf(AI编程IDE)、以及通过MCP网关接入的GPT系列模型。只要平台支持MCP协议,即可直接使用。
我们采用多层安全机制:1) API Key 认证,每个请求需携带有效Token;2) 权限隔离,不同API Key可配置不同的操作权限;3) TLS 1.3 加密传输;4) 请求频率限制,防止滥用;5) 完整的操作审计日志。
MCP Server 让您可以通过自然语言与大模型交互来完成开票,无需编写代码。例如直接说"帮我开一张发票"即可。而传统API需要编写代码调用。两者底层调用的是同一套发票API,功能和性能完全一致。MCP Server 更适合非技术用户和追求效率的场景。
是的!MCP Server 功能包含在所有定价方案中,包括1分钱试用版。我们致力于推动AI开票的普及,不对此功能额外收费。