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 兼容的大模型客户端
// MCP Server 端点信息
{
  "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

1

点击 open.fa-piao.com 注册并登录

2

申请 MCP Token

登录后,点击申请 MCP Token

3

MCP Token 申请成功,可以一键复制

2 多语言接入代码示例

选择编程语言和调用模式,查看对应的 MCP 客户端接入代码。所有示例均基于 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  # 设置为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调用测试...")


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  # 设置为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调用测试...")


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 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);
}
  
Node.js — 非流式
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);
}

Java — 流式
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\"}}");
    }
}

Java — 非流式
 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\"}}");
    }
}
 
Go — 流式
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)
}


Go — 非流式
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)
}

C# — 流式
 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\"}}");
        }
    }
}
 
C# — 非流式
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\"}}");
        }
    }
}

PHP — 流式
 
 '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";
    }
}

PHP — 非流式
 '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";
    }
}

Rust — 流式
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?;

Rust — 非流式
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> = Mutex::new(None);
}

fn get_session_id() -> Option {
    let session_id = MCP_SESSION_ID.lock().unwrap();
    session_id.clone()
}

fn set_session_id(session_id: Option) {
    let mut sid = MCP_SESSION_ID.lock().unwrap();
    *sid = session_id;
}

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()
    }
}

pub async fn mcp_call(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"));

    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 response_session_id = response_headers
        .get(HeaderName::from_static("mcp-session-id"))
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());

    if let Some(session_id) = &response_session_id {
        set_session_id(Some(session_id.clone()));
    }

    let response_body = response.text().await?;
    let duration = start_time.elapsed();
    let duration_ms = duration.as_secs_f64() * 1000.0;

    // ====================== 只有 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.trim().to_string())
}

pub async fn initialize() -> Result> {
    mcp_call("initialize", Some(r#"{"protocolVersion": "2025-11-25"}"#)).await
}

pub async fn notifications_initialized() -> Result> {
    mcp_call("notifications/initialized", Some("{}")).await
}

pub async fn tools_list() -> Result> {
    mcp_call("tools/list", None).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("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": "91500112MAXXXX",
        "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"
lazy_static = "1.4"

// 直接调用
let response = mcp_call("tools/call", Some(r#"{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX"}}"#)).await?;

// 或使用便捷函数
initialize().await?;
let args = serde_json::json!({
    "nsrsbh": "91500112MAXXXX",
    "username": "1325580xxxx"
});
tools_call("enterprise_query_state", &args).await?;


3. 各平台接入教程

Cherry Studio

前置条件:需申请到 MCP Token,教程:申请 MCP Token

参考 Cherry Studio 官方文档:docs.cherry-ai.com/advanced-basic/mcp

1、打开 Cherry Studio,进入设置页面

2、选择「MCP」选项卡

3、点击「添加」按钮

4、在弹出的下拉框中,选择「从 JSON 导入」

Cherry Studio 从 JSON 导入

从上面复制 JSON 粘贴进去,一定记得替换 "YOUR_MCP_TOKEN",然后点击「确定」按钮

Cherry Studio 粘贴 JSON 配置

添加完成后,请打开启用开关

Cherry Studio 启用开关

配置完成。现在可以在聊天窗口中使用 MCP 功能。

Cherry Studio 聊天窗口使用 MCP

Cursor

前置条件:需申请到 MCP Token,教程:申请 MCP Token

参考 Cursor 官方文档:cursor.com/cn/docs/context/mcp

打开 Cursor,点击顶部菜单栏【设置】→【Tools & MCP】,在 Installed MCP Servers 中点击【Add Custom MCP】

Cursor Add Custom MCP

在打开的 mcp.json 文件中填入从上面复制的 JSON 内容,一定记得替换 YOUR_MCP_TOKEN 为实际 MCP Token,点击【关闭】并选择【保存】

Cursor 编辑 mcp.json

回到设置页面中,此时应显示可用的 MCP 工具,服务状态应显示为【已连接】

Cursor MCP 已连接

按下 CTRL/CMD + L 打开右侧 Agent 对话框,接下来就可以直接在对话框中输入需求,让 AI 为我们调用工具了

TRAE

前置条件:需申请到 MCP Token,教程:申请 MCP Token

参考 TRAE 官方文档:docs.trae.cn/ide/model-context-protocol

打开 Trae,点击【设置】→【MCP】→【手动添加】进行添加

TRAE 设置 MCP TRAE 手动添加 MCP

在打开的手动配置页面中填入从上面复制的 JSON 内容,一定记得替换 YOUR_MCP_TOKEN 为实际 MCP Token,点击【确认】

TRAE 粘贴 JSON 配置

回到 MCP 页面中,此时应显示可用的 MCP 工具,服务状态应显示为【已连接】

TRAE MCP 已连接

回到对话框中,选择【Builder with MCP】

TRAE Builder with MCP

接下来就可以直接在对话框中输入需求,让 AI 为我们调用工具了

使用场景

AI 开票的典型应用

大模型 + 发票API,开启智能财税新时代

💬

对话式开票

直接对大模型说"帮我开一张发票,购买方是XX公司,金额1000元",AI自动完成开票全流程并返回结果。

📊

批量智能处理

上传Excel订单列表,AI自动逐条解析并批量开具发票,大幅提升财务工作效率。

🔍

智能发票查验

AI自动识别发票信息并调用查验接口,快速验证发票真伪,防止假发票入账。

🔄

自动化财税流程

结合RPA和AI,实现从订单到开票到入账的全流程自动化,减少人工干预。

🏗️

企业AI中台集成

将发票API作为企业AI中台的标准工具,供内部多个AI应用统一调用。

📱

AI客服自动开票

智能客服机器人直接为用户开具发票,提升客户服务体验和响应速度。

常见问题

MCP Server 常见疑问

什么是 MCP (Model Context Protocol)?

MCP 是由 Anthropic 推出的开放标准协议,用于规范AI大模型与外部工具/API之间的通信。它定义了统一的接口格式,让任何支持MCP的大模型都能标准化地调用外部服务。fa-piao.com 的 MCP Server 实现了 Streamable HTTP 传输方式,支持实时流式响应。

支持哪些大模型平台?

目前支持所有兼容 MCP 协议的大模型平台,包括:Claude Desktop(Anthropic官方客户端)、Cursor/Windsurf(AI编程IDE)、以及通过MCP网关接入的GPT系列模型。只要平台支持MCP协议,即可直接使用。

MCP Server 的安全性如何保障?

我们采用多层安全机制:1) API Key 认证,每个请求需携带有效Token;2) 权限隔离,不同API Key可配置不同的操作权限;3) TLS 1.3 加密传输;4) 请求频率限制,防止滥用;5) 完整的操作审计日志。

使用 MCP Server 开票和直接调用 API 有什么区别?

MCP Server 让您可以通过自然语言与大模型交互来完成开票,无需编写代码。例如直接说"帮我开一张发票"即可。而传统API需要编写代码调用。两者底层调用的是同一套发票API,功能和性能完全一致。MCP Server 更适合非技术用户和追求效率的场景。

MCP Server 是否包含在所有定价方案中?

是的!MCP Server 功能包含在所有定价方案中,包括1分钱试用版。我们致力于推动AI开票的普及,不对此功能额外收费。

立即体验 AI 智能开票

1分钱试用14天,含 MCP Server 全功能

获取 API Key 开始配置