Skip to content

fapiaoapi/tax-invoice-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 

Repository files navigation

fa-piao.com MCP Server

MCP API

让 AI 大模型成为您的开票助手 — 通过 MCP (Model Context Protocol) 协议,将电子发票/数电发票 API 接入 Claude、GPT、Cursor、Cherry Studio 等大模型平台,实现 AI 智能开票。


目录


什么是 MCP Server?

MCP(Model Context Protocol)是由 Anthropic 推出的开放标准协议,定义了 AI 大模型如何安全、标准化地调用外部工具和 API。

fa-piao.com 率先实现了 MCP Server (Streamable HTTP),让任何支持 MCP 的大模型都能直接操作发票系统:

  • Streamable HTTP 传输,实时流式响应
  • 完整的 Tools 定义:开票 / 查询 / 红冲 / 查验
  • 自然语言交互,无需编写代码
  • 企业级 API Key 权限隔离
  • 支持所有 MCP 兼容的大模型客户端

核心特性

特性 说明
🔌 Streamable HTTP 基于 HTTP 的流式传输协议,实时推送响应
🛠️ 完整 Tools 定义 开票、查询、红冲、查验等全部发票操作
🔐 安全认证 Bearer Token 认证 + TLS 1.3 加密传输
🌍 多语言支持 Python / Node.js / Java / Go / C# / PHP / Rust
🤖 大模型兼容 Claude / GPT / Cursor / Cherry Studio / TRAE
📊 调试模式 内置 DEBUG 开关,完整请求/响应日志

快速开始

1. 获取 MCP Token

访问 open.fa-piao.com 注册并登录,申请 MCP Token。

2. 配置 MCP Server

在大模型客户端的配置文件中添加以下 JSON:

{
  "mcpServers": {
    "tax-invoice-mcp": {
      "type": "streamableHttp",
      "url": "https://mcp.fa-piao.com",
      "headers": {
        "Authorization": "Bearer YOUR_MCP_TOKEN",
        "X-Client-Version": "1.0.1"
      }
    }
  }
}

⚠️ 重要:请将 YOUR_MCP_TOKEN 替换为实际的 MCP Token。

3. 开始使用

配置完成后,即可通过自然语言与大模型交互,例如:

  • "查询纳税人 91500112MAXXXXX 的开票状态"
  • "查验这张发票的真伪"

MCP Server 配置

配置项 说明
协议类型 streamableHttp Streamable HTTP 传输协议
端点地址 https://mcp.fa-piao.com MCP Server 服务地址
认证方式 Bearer Token 在 Header 中携带 Authorization
客户端版本 1.0.1 X-Client-Version 请求头

请求头说明

Header 必填 说明
Authorization Bearer Token 认证
Content-Type application/json
X-Client-Version 客户端版本号
Accept 流式必填 text/event-stream, application/json
mcp-session-id 自动 服务端返回的会话 ID

多语言接入示例

所有示例均基于 JSON-RPC 2.0 协议,支持流式和非流式两种调用模式。

Python

流式调用
import requests
import json
import time
from datetime import datetime

MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"
DEBUG = True

mcp_session_id = None
request_id_counter = 0


def get_next_request_id():
    global request_id_counter
    request_id_counter += 1
    return request_id_counter


def mcp_call_stream(method, params=None):
    global mcp_session_id
    start_time = time.time()
    current_id = get_next_request_id()

    payload = {
        "jsonrpc": "2.0",
        "id": current_id,
        "method": method,
        "params": params if params is not None else {}
    }

    body = json.dumps(payload, ensure_ascii=False)

    headers = {
        "Authorization": f"Bearer {MCP_TOKEN}",
        "Content-Type": "application/json",
        "X-Client-Version": "1.0.1",
        "Accept": "text/event-stream, application/json"
    }

    if mcp_session_id:
        headers["mcp-session-id"] = mcp_session_id

    try:
        resp = requests.post(MCP_URL, data=body, headers=headers, timeout=120)
        duration_ms = (time.time() - start_time) * 1000

        response_session_id = resp.headers.get("mcp-session-id")
        if response_session_id:
            mcp_session_id = response_session_id

        content_type = resp.headers.get("Content-Type", "")
        is_sse = "text/event-stream" in content_type

        if is_sse:
            response_data = []
            for line in resp.iter_lines(decode_unicode=True):
                if not line:
                    continue
                if line.startswith("data:"):
                    data = line[5:].strip()
                    if data == "[DONE]":
                        break
                    response_data.append(data)
            return "\n".join(response_data)
        else:
            return resp.text.strip()

    except Exception as e:
        print(f"[异常] {method}: {e}")
        return None


if __name__ == "__main__":
    mcp_call_stream("initialize", {"protocolVersion": "2025-11-25"})
    mcp_call_stream("notifications/initialized", {})
    mcp_call_stream("tools/list", {})
    mcp_call_stream("tools/call", {
        "name": "enterprise_query_state",
        "arguments": {"nsrsbh": "91500112MAXXXXX", "username": "1325580xxxx"}
    })
非流式调用
import requests
import json

MCP_URL = "https://mcp.fa-piao.com"
MCP_TOKEN = "YOUR_MCP_TOKEN"

mcp_session_id = None
request_id_counter = 0


def get_next_request_id():
    global request_id_counter
    request_id_counter += 1
    return request_id_counter


def mcp_call(method, params=None):
    global mcp_session_id
    current_id = get_next_request_id()

    payload = {
        "jsonrpc": "2.0",
        "id": current_id,
        "method": method,
        "params": params if params is not None else {}
    }

    headers = {
        "Authorization": f"Bearer {MCP_TOKEN}",
        "X-Client-Version": "1.0.1",
        "Content-Type": "application/json"
    }

    if mcp_session_id:
        headers["mcp-session-id"] = mcp_session_id

    resp = requests.post(MCP_URL, data=json.dumps(payload, ensure_ascii=False),
                         headers=headers, timeout=120)

    response_session_id = resp.headers.get("mcp-session-id")
    if response_session_id:
        mcp_session_id = response_session_id

    return resp.text.strip()


if __name__ == "__main__":
    mcp_call("initialize", {"protocolVersion": "2025-11-25"})
    result = mcp_call("tools/call", {
        "name": "enterprise_query_state",
        "arguments": {"nsrsbh": "91500112MAXXXX", "username": "1325580xxxx"}
    })
    print(json.dumps(result, indent=2, ensure_ascii=False))

Node.js

流式调用
import axios, { AxiosResponse } from 'axios';

const MCP_URL = 'https://mcp.fa-piao.com';
const MCP_TOKEN = 'YOUR_MCP_TOKEN';
const DEBUG = true;

let mcpSessionId: string | null = null;
let requestIdCounter = 0;

function getNextRequestId(): number {
  requestIdCounter += 1;
  return requestIdCounter;
}

function parseSseResponse(responseText: string): string {
  const lines = responseText.split(/\r?\n/);
  const responseData: string[] = [];
  for (const line of lines) {
    if (!line.trim()) continue;
    if (line.startsWith('data:')) {
      const data = line.substring(5).trim();
      if (data === '[DONE]') break;
      responseData.push(data);
    }
  }
  return responseData.join('\n');
}

export async function mcpCallStream(method: string, params?: any): Promise<string | null> {
  const startTime = Date.now();
  const currentId = getNextRequestId();

  const payload = {
    jsonrpc: '2.0',
    id: currentId,
    method: method,
    params: params !== undefined && params !== null ? params : {}
  };

  const headers: Record<string, string> = {
    'Authorization': `Bearer ${MCP_TOKEN}`,
    'Content-Type': 'application/json',
    'X-Client-Version': '1.0.1',
    'Accept': 'text/event-stream, application/json'
  };

  if (mcpSessionId) {
    headers['mcp-session-id'] = mcpSessionId;
  }

  try {
    const response: AxiosResponse = await axios.post(MCP_URL, JSON.stringify(payload), {
      headers: headers,
      timeout: 120000,
      validateStatus: () => true,
      responseType: 'text',
      transformResponse: [(data) => data]
    });

    const responseSessionId = response.headers['mcp-session-id'];
    if (responseSessionId && !Array.isArray(responseSessionId)) {
      mcpSessionId = responseSessionId;
    }

    const contentType = response.headers['content-type'] || '';
    const isSse = contentType.includes('text/event-stream');

    if (isSse) {
      return parseSseResponse(response.data);
    }
    return response.data.trim();
  } catch (error) {
    console.error(`[异常] ${method}:`, error);
    return null;
  }
}

async function main() {
  await mcpCallStream('initialize', { protocolVersion: '2025-11-25' });
  await mcpCallStream('notifications/initialized', {});
  await mcpCallStream('tools/list', {});
  await mcpCallStream('tools/call', {
    name: 'enterprise_query_state',
    arguments: { nsrsbh: '91500112MAXXXXX', username: '1325580xxxx' }
  });
}

if (require.main === module) {
  main().catch(console.error);
}
非流式调用
import axios, { AxiosResponse } from 'axios';

const MCP_URL = 'https://mcp.fa-piao.com';
const MCP_TOKEN = 'YOUR_MCP_TOKEN';

let mcpSessionId: string | null = null;
let requestId = 0;

function nextRequestId(): number {
  return ++requestId;
}

export async function mcpCall(method: string, params?: string): Promise<string> {
  const currentId = nextRequestId();
  const body = JSON.stringify({
    jsonrpc: '2.0',
    id: currentId,
    method: method,
    params: params ? JSON.parse(params) : {}
  });

  const headers: Record<string, string> = {
    'Authorization': `Bearer ${MCP_TOKEN}`,
    'X-Client-Version': '1.0.1',
    'Content-Type': 'application/json'
  };

  if (mcpSessionId) {
    headers['mcp-session-id'] = mcpSessionId;
  }

  const response: AxiosResponse = await axios.post(MCP_URL, body, {
    headers: headers,
    timeout: 30000,
    validateStatus: () => true,
    transformResponse: []
  });

  const responseSessionId = response.headers['mcp-session-id'];
  if (responseSessionId && !Array.isArray(responseSessionId)) {
    mcpSessionId = responseSessionId;
  }

  return typeof response.data === 'string' ? response.data : JSON.stringify(response.data);
}

async function main() {
  await mcpCall('initialize', '{"protocolVersion": "2025-11-25"}');
  const result = await mcpCall('tools/call',
    '{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}');
  console.log(result);
}

if (require.main === module) {
  main().catch(console.error);
}

Java

流式调用
import java.net.URI;
import java.net.http.*;
import java.io.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class McpStreamClient {
    public static final boolean DEBUG = false;
    private static final String MCP_URL = "https://mcp.fa-piao.com";
    private static final String MCP_TOKEN = "YOUR_MCP_TOKEN";
    private static final AtomicReference<String> mcpSessionId = new AtomicReference<>(null);
    private static final AtomicInteger requestId = new AtomicInteger(0);

    private static int nextRequestId() {
        return requestId.incrementAndGet();
    }

    public static String mcpCallStream(String method, String params) throws Exception {
        int currentId = nextRequestId();
        String body = String.format(
            "{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"%s\",\"params\":%s}",
            currentId, method, params != null ? params : "{}"
        );

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
            .uri(URI.create(MCP_URL))
            .header("Authorization", "Bearer " + MCP_TOKEN)
            .header("X-Client-Version", "1.0.1")
            .header("Content-Type", "application/json")
            .header("Accept", "text/event-stream, application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body));

        String sessionId = mcpSessionId.get();
        if (sessionId != null && !sessionId.isEmpty()) {
            requestBuilder.header("mcp-session-id", sessionId);
        }

        HttpClient client = HttpClient.newHttpClient();
        HttpResponse<InputStream> resp = client.send(
            requestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream());

        String responseSessionId = resp.headers().firstValue("mcp-session-id").orElse(null);
        if (responseSessionId != null) {
            mcpSessionId.set(responseSessionId);
        }

        return readStreamResponse(resp.body(), resp.headers());
    }

    private static String readStreamResponse(InputStream inputStream, HttpHeaders respHeaders)
            throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder responseBody = new StringBuilder();
        String line;
        String contentType = respHeaders.firstValue("Content-Type").orElse("");
        boolean isSSE = contentType.contains("text/event-stream");

        if (isSSE) {
            while ((line = reader.readLine()) != null) {
                if (line.startsWith("data:")) {
                    String data = line.substring(5).trim();
                    if ("[DONE]".equals(data)) break;
                    responseBody.append(data);
                }
            }
        } else {
            int ch;
            while ((ch = reader.read()) != -1) {
                responseBody.append((char) ch);
            }
        }
        reader.close();
        return responseBody.toString().trim();
    }

    public static void main(String[] args) throws Exception {
        mcpCallStream("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
        mcpCallStream("notifications/initialized", "{}");
        mcpCallStream("tools/list", "{}");
        mcpCallStream("tools/call",
            "{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXXX\",\"username\":\"1325580xxxx\"}}");
    }
}
非流式调用
import java.net.URI;
import java.net.http.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

public class McpClient {
    private static final String MCP_URL = "https://mcp.fa-piao.com";
    private static final String MCP_TOKEN = "YOUR_MCP_TOKEN";
    private static final AtomicReference<String> mcpSessionId = new AtomicReference<>(null);
    private static final AtomicInteger requestId = new AtomicInteger(0);

    private static int nextRequestId() {
        return requestId.incrementAndGet();
    }

    public static String mcpCall(String method, String params) throws Exception {
        int currentId = nextRequestId();
        String body = String.format(
            "{\"jsonrpc\":\"2.0\",\"id\":%d,\"method\":\"%s\",\"params\":%s}",
            currentId, method, params != null ? params : "{}"
        );

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
            .uri(URI.create(MCP_URL))
            .header("Authorization", "Bearer " + MCP_TOKEN)
            .header("X-Client-Version", "1.0.1")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body));

        String sessionId = mcpSessionId.get();
        if (sessionId != null && !sessionId.isEmpty()) {
            requestBuilder.header("mcp-session-id", sessionId);
        }

        HttpClient client = HttpClient.newHttpClient();
        HttpResponse<String> resp = client.send(
            requestBuilder.build(), HttpResponse.BodyHandlers.ofString());

        String responseSessionId = resp.headers().firstValue("mcp-session-id").orElse(null);
        if (responseSessionId != null) {
            mcpSessionId.set(responseSessionId);
        }

        return resp.body();
    }

    public static void main(String[] args) throws Exception {
        mcpCall("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
        mcpCall("tools/call",
            "{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXX\",\"username\":\"1325580xxxx\"}}");
    }
}

Go

流式调用
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync/atomic"
)

const (
	MCP_URL   = "https://mcp.fa-piao.com"
	MCP_TOKEN = "YOUR_MCP_TOKEN"
	DEBUG     = true
)

var (
	mcpSessionId atomic.Value
	requestId    atomic.Int32
)

func init() {
	mcpSessionId.Store("")
}

func nextRequestId() int32 {
	return requestId.Add(1)
}

func mcpCallStream(method string, params string) (string, error) {
	currentId := nextRequestId()
	body := fmt.Sprintf(`{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}`,
		currentId, method, params)

	req, err := http.NewRequest("POST", MCP_URL, bytes.NewBufferString(body))
	if err != nil {
		return "", fmt.Errorf("创建请求失败: %v", err)
	}

	req.Header.Set("Authorization", "Bearer "+MCP_TOKEN)
	req.Header.Set("X-Client-Version", "1.0.1")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "text/event-stream, application/json")

	sessionId := mcpSessionId.Load().(string)
	if sessionId != "" {
		req.Header.Set("mcp-session-id", sessionId)
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("发送请求失败: %v", err)
	}
	defer resp.Body.Close()

	responseSessionId := resp.Header.Get("mcp-session-id")
	if responseSessionId != "" {
		mcpSessionId.Store(responseSessionId)
	}

	return readStreamResponse(resp.Body, resp.Header.Get("Content-Type"))
}

func readStreamResponse(body io.Reader, contentType string) (string, error) {
	reader := bufio.NewReader(body)
	var responseBody strings.Builder
	isSSE := strings.Contains(contentType, "text/event-stream")

	if isSSE {
		for {
			line, err := reader.ReadString('\n')
			if err != nil {
				if err == io.EOF {
					break
				}
				return "", err
			}
			line = strings.TrimSpace(line)
			if strings.HasPrefix(line, "data:") {
				data := strings.TrimSpace(line[5:])
				if data == "[DONE]" {
					break
				}
				responseBody.WriteString(data)
			}
		}
	} else {
		all, err := io.ReadAll(reader)
		if err != nil {
			return "", err
		}
		responseBody.Write(all)
	}

	return strings.TrimSpace(responseBody.String()), nil
}

func main() {
	mcpCallStream("initialize", `{"protocolVersion": "2025-11-25"}`)
	mcpCallStream("notifications/initialized", "{}")
	mcpCallStream("tools/list", "{}")
	mcpCallStream("tools/call",
		`{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXXX","username":"1325580xxxx"}}`)
}
非流式调用
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"sync/atomic"
)

const (
	MCP_URL   = "https://mcp.fa-piao.com"
	MCP_TOKEN = "YOUR_MCP_TOKEN"
)

var (
	mcpSessionId atomic.Value
	requestId    atomic.Int32
)

func init() {
	mcpSessionId.Store("")
}

func nextRequestId() int32 {
	return requestId.Add(1)
}

func mcpCall(method string, params string) (string, error) {
	currentId := nextRequestId()
	body := fmt.Sprintf(`{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}`,
		currentId, method, params)

	req, err := http.NewRequest("POST", MCP_URL, bytes.NewBufferString(body))
	if err != nil {
		return "", fmt.Errorf("创建请求失败: %v", err)
	}

	req.Header.Set("Authorization", "Bearer "+MCP_TOKEN)
	req.Header.Set("X-Client-Version", "1.0.1")
	req.Header.Set("Content-Type", "application/json")

	sessionId := mcpSessionId.Load().(string)
	if sessionId != "" {
		req.Header.Set("mcp-session-id", sessionId)
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("发送请求失败: %v", err)
	}
	defer resp.Body.Close()

	responseSessionId := resp.Header.Get("mcp-session-id")
	if responseSessionId != "" {
		mcpSessionId.Store(responseSessionId)
	}

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("读取响应失败: %v", err)
	}

	return string(respBody), nil
}

func main() {
	mcpCall("initialize", `{"protocolVersion": "2025-11-25"}`)
	mcpCall("tools/call",
		`{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}`)
}

C#

流式调用
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Tax.Invoice.Example
{
    public class McpStreamClient
    {
        public static readonly bool DEBUG = true;
        private const string MCP_URL = "https://mcp.fa-piao.com";
        private const string MCP_TOKEN = "YOUR_MCP_TOKEN";
        private static string? mcpSessionId = null;
        private static int requestId = 0;
        private static readonly object lockObj = new object();

        private static int NextRequestId()
        {
            lock (lockObj) { return ++requestId; }
        }

        public static async Task<string> McpCallStream(string method, string? paramsJson = null)
        {
            int currentId = NextRequestId();
            string body = string.Format(
                "{{\"jsonrpc\":\"2.0\",\"id\":{0},\"method\":\"{1}\",\"params\":{2}}}",
                currentId, method, !string.IsNullOrEmpty(paramsJson) ? paramsJson : "{}"
            );

            using var requestBuilder = new HttpRequestMessage(HttpMethod.Post, MCP_URL);
            requestBuilder.Headers.Authorization =
                new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", MCP_TOKEN);
            requestBuilder.Headers.TryAddWithoutValidation("X-Client-Version", "1.0.1");
            requestBuilder.Headers.TryAddWithoutValidation("Accept", "text/event-stream, application/json");

            if (!string.IsNullOrEmpty(mcpSessionId))
                requestBuilder.Headers.TryAddWithoutValidation("mcp-session-id", mcpSessionId);

            requestBuilder.Content = new StringContent(body, Encoding.UTF8, "application/json");

            using var client = new HttpClient();
            var response = await client.SendAsync(requestBuilder);

            var responseSessionId = response.Headers.GetValues("mcp-session-id").FirstOrDefault();
            if (!string.IsNullOrEmpty(responseSessionId))
                mcpSessionId = responseSessionId;

            return await ReadStreamResponse(
                await response.Content.ReadAsStreamAsync(),
                response.Content.Headers.ContentType?.MediaType ?? "");
        }

        private static async Task<string> ReadStreamResponse(Stream stream, string contentType)
        {
            using var reader = new StreamReader(stream, Encoding.UTF8);
            var responseBody = new StringBuilder();
            bool isSSE = contentType.Contains("text/event-stream");

            if (isSSE)
            {
                string? line;
                while ((line = await reader.ReadLineAsync()) != null)
                {
                    if (line.StartsWith("data:"))
                    {
                        string data = line.Substring(5).Trim();
                        if ("[DONE]".Equals(data)) break;
                        responseBody.Append(data);
                    }
                }
            }
            else
            {
                responseBody.Append(await reader.ReadToEndAsync());
            }

            return responseBody.ToString().Trim();
        }

        public static async Task Main(string[] args)
        {
            await McpCallStream("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
            await McpCallStream("notifications/initialized", "{}");
            await McpCallStream("tools/list", "{}");
            await McpCallStream("tools/call",
                "{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXXX\",\"username\":\"1325580xxxx\"}}");
        }
    }
}
非流式调用
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Tax.Invoice.Example
{
    public class McpClient
    {
        private const string MCP_URL = "https://mcp.fa-piao.com";
        private const string MCP_TOKEN = "YOUR_MCP_TOKEN";
        private static string? mcpSessionId = null;
        private static int requestId = 0;
        private static readonly object lockObj = new object();

        private static int NextRequestId()
        {
            lock (lockObj) { return ++requestId; }
        }

        public static async Task<string> McpCall(string method, string? paramsJson = null)
        {
            int currentId = NextRequestId();
            string body = string.Format(
                "{{\"jsonrpc\":\"2.0\",\"id\":{0},\"method\":\"{1}\",\"params\":{2}}}",
                currentId, method, !string.IsNullOrEmpty(paramsJson) ? paramsJson : "{}"
            );

            using var requestBuilder = new HttpRequestMessage(HttpMethod.Post, MCP_URL);
            requestBuilder.Headers.Authorization =
                new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", MCP_TOKEN);
            requestBuilder.Headers.TryAddWithoutValidation("X-Client-Version", "1.0.1");

            if (!string.IsNullOrEmpty(mcpSessionId))
                requestBuilder.Headers.TryAddWithoutValidation("mcp-session-id", mcpSessionId);

            requestBuilder.Content = new StringContent(body, Encoding.UTF8, "application/json");

            using var client = new HttpClient();
            var response = await client.SendAsync(requestBuilder);

            var responseSessionId = response.Headers.GetValues("mcp-session-id").FirstOrDefault();
            if (!string.IsNullOrEmpty(responseSessionId))
                mcpSessionId = responseSessionId;

            return await response.Content.ReadAsStringAsync();
        }

        public static async Task Main(string[] args)
        {
            await McpCall("initialize", "{\"protocolVersion\": \"2025-11-25\"}");
            await McpCall("tools/call",
                "{\"name\":\"enterprise_query_state\",\"arguments\":{\"nsrsbh\":\"91500112MAXXXX\",\"username\":\"1325580xxxx\"}}");
        }
    }
}

PHP

流式调用
<?php

class McpStreamClient
{
    const DEBUG = false;
    private static $MCP_URL = "https://mcp.fa-piao.com";
    private static $MCP_TOKEN = "YOUR_MCP_TOKEN";
    private static $mcpSessionId = null;
    private static $requestId = 0;

    private static function nextRequestId()
    {
        return ++self::$requestId;
    }

    public static function mcpCallStream($method, $params = null)
    {
        $currentId = self::nextRequestId();
        $paramsJson = $params !== null ? $params : '{}';
        $body = json_encode([
            'jsonrpc' => '2.0',
            'id' => $currentId,
            'method' => $method,
            'params' => json_decode($paramsJson, true)
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $headers = [
            'Authorization: Bearer ' . self::$MCP_TOKEN,
            'X-Client-Version: 1.0.1',
            'Content-Type: application/json',
            'Accept: text/event-stream, application/json'
        ];

        if (self::$mcpSessionId !== null && self::$mcpSessionId !== '') {
            $headers[] = 'mcp-session-id: ' . self::$mcpSessionId;
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => self::$MCP_URL,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_TIMEOUT => 30,
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

        if (curl_errno($ch)) {
            curl_close($ch);
            throw new Exception("cURL Error: " . curl_error($ch));
        }
        curl_close($ch);

        $responseHeaders = substr($response, 0, $headerSize);
        $responseBody = trim(substr($response, $headerSize));

        preg_match_all('/^mcp-session-id:\s*(.+)$/im', $responseHeaders, $matches);
        if (!empty($matches[1])) {
            self::$mcpSessionId = trim($matches[1][0]);
        }

        return $responseBody;
    }

    public static function initialize()
    {
        return self::mcpCallStream("initialize", '{"protocolVersion": "2025-11-25"}');
    }

    public static function toolsCall($toolName, $arguments)
    {
        $params = json_encode(['name' => $toolName, 'arguments' => $arguments],
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        return self::mcpCallStream("tools/call", $params);
    }
}

// 使用示例
McpStreamClient::initialize();
McpStreamClient::toolsCall("enterprise_query_state", [
    "nsrsbh" => "91500112MAXXXXX",
    "username" => "1325580xxxx"
]);
非流式调用
<?php

class McpClient
{
    const DEBUG = true;
    private static $MCP_URL = "https://mcp.fa-piao.com";
    private static $MCP_TOKEN = "YOUR_MCP_TOKEN";
    private static $mcpSessionId = null;
    private static $requestId = 0;

    private static function nextRequestId()
    {
        return ++self::$requestId;
    }

    public static function mcpCall($method, $params = null)
    {
        $currentId = self::nextRequestId();
        $paramsJson = $params !== null ? $params : '{}';
        $body = json_encode([
            'jsonrpc' => '2.0',
            'id' => $currentId,
            'method' => $method,
            'params' => json_decode($paramsJson, true)
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $headers = [
            'Authorization: Bearer ' . self::$MCP_TOKEN,
            'X-Client-Version: 1.0.1',
            'Content-Type: application/json'
        ];

        if (self::$mcpSessionId !== null && self::$mcpSessionId !== '') {
            $headers[] = 'mcp-session-id: ' . self::$mcpSessionId;
        }

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => self::$MCP_URL,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HEADER => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_TIMEOUT => 30,
        ]);

        $response = curl_exec($ch);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);

        if (curl_errno($ch)) {
            curl_close($ch);
            throw new Exception("cURL Error: " . curl_error($ch));
        }
        curl_close($ch);

        $responseHeaders = substr($response, 0, $headerSize);
        $responseBody = trim(substr($response, $headerSize));

        preg_match_all('/^mcp-session-id:\s*(.+)$/im', $responseHeaders, $matches);
        if (!empty($matches[1])) {
            self::$mcpSessionId = trim($matches[1][0]);
        }

        return $responseBody;
    }

    public static function initialize()
    {
        return self::mcpCall("initialize", '{"protocolVersion": "2025-11-25"}');
    }

    public static function toolsCall($toolName, $arguments)
    {
        $params = json_encode(['name' => $toolName, 'arguments' => $arguments],
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        return self::mcpCall("tools/call", $params);
    }
}

// 使用示例
McpClient::initialize();
McpClient::toolsCall("enterprise_query_state", [
    "nsrsbh" => "91500112MAXXXX",
    "username" => "1325580xxxx"
]);

Rust

流式调用
use std::io::{self, BufRead};
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, ACCEPT, AUTHORIZATION, CONTENT_TYPE};

const MCP_URL: &str = "https://mcp.fa-piao.com";
const MCP_TOKEN: &str = "YOUR_MCP_TOKEN";
const CLIENT_VERSION: &str = "1.0.1";

lazy_static::lazy_static! {
    static ref MCP_SESSION_ID: Mutex<Option<String>> = Mutex::new(None);
}
static REQUEST_ID: AtomicI32 = AtomicI32::new(0);

fn get_session_id() -> Option<String> {
    MCP_SESSION_ID.lock().unwrap().clone()
}

fn set_session_id(session_id: Option<String>) {
    *MCP_SESSION_ID.lock().unwrap() = session_id;
}

fn next_request_id() -> i32 {
    REQUEST_ID.fetch_add(1, Ordering::SeqCst) + 1
}

async fn read_stream_response(
    body: reqwest::Response, content_type: &str,
) -> Result<String, Box<dyn std::error::Error>> {
    let is_sse = content_type.contains("text/event-stream");
    if is_sse {
        let mut response_body = String::new();
        let reader = io::BufReader::new(body.bytes().await?);
        for line in reader.lines() {
            let line = line?;
            if line.starts_with("data:") {
                let data = line[5..].trim().to_string();
                if data == "[DONE]" { break; }
                response_body.push_str(&data);
            }
        }
        Ok(response_body.trim().to_string())
    } else {
        let bytes = body.bytes().await?;
        Ok(String::from_utf8(bytes.to_vec())?.trim().to_string())
    }
}

pub async fn mcp_call_stream(
    method: &str, params: Option<&str>,
) -> Result<String, Box<dyn std::error::Error>> {
    let current_id = next_request_id();
    let params_json = params.unwrap_or("{}");
    let body = format!(
        r#"{{"jsonrpc":"2.0","id":{},"method":"{}","params":{}}}"#,
        current_id, method, params_json
    );

    let mut headers = HeaderMap::new();
    headers.insert(AUTHORIZATION,
        HeaderValue::from_str(&format!("Bearer {}", MCP_TOKEN))?);
    headers.insert(HeaderName::from_static("x-client-version"),
        HeaderValue::from_static(CLIENT_VERSION));
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    headers.insert(ACCEPT,
        HeaderValue::from_static("text/event-stream, application/json"));

    if let Some(session_id) = get_session_id() {
        headers.insert(HeaderName::from_static("mcp-session-id"),
            HeaderValue::from_str(&session_id)?);
    }

    let client = reqwest::Client::new();
    let response = client.post(MCP_URL).headers(headers).body(body).send().await?;

    let response_session_id = response.headers()
        .get(HeaderName::from_static("mcp-session-id"))
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());
    set_session_id(response_session_id);

    let content_type = response.headers()
        .get(CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .unwrap_or("");

    read_stream_response(response, content_type).await
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    mcp_call_stream("initialize", Some(r#"{"protocolVersion": "2025-11-25"}"#)).await?;
    mcp_call_stream("notifications/initialized", Some("{}")).await?;
    mcp_call_stream("tools/list", Some("{}")).await?;
    mcp_call_stream("tools/call",
        Some(r#"{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXXX","username":"1325580xxxx"}}"#)).await?;
    Ok(())
}
非流式调用
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Mutex;
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, AUTHORIZATION, CONTENT_TYPE};

const MCP_URL: &str = "https://mcp.fa-piao.com";
const MCP_TOKEN: &str = "YOUR_MCP_TOKEN";
const CLIENT_VERSION: &str = "1.0.1";

lazy_static::lazy_static! {
    static ref MCP_SESSION_ID: Mutex<Option<String>> = Mutex::new(None);
}
static REQUEST_ID: AtomicI32 = AtomicI32::new(0);

fn get_session_id() -> Option<String> {
    MCP_SESSION_ID.lock().unwrap().clone()
}

fn set_session_id(session_id: Option<String>) {
    *MCP_SESSION_ID.lock().unwrap() = session_id;
}

fn next_request_id() -> i32 {
    REQUEST_ID.fetch_add(1, Ordering::SeqCst) + 1
}

pub async fn mcp_call(
    method: &str, params: Option<&str>,
) -> Result<String, Box<dyn std::error::Error>> {
    let current_id = next_request_id();
    let params_json = params.unwrap_or("{}");
    let body = format!(
        r#"{{"jsonrpc":"2.0","id":{},"method":"{}","params":{}}}"#,
        current_id, method, params_json
    );

    let mut headers = HeaderMap::new();
    headers.insert(AUTHORIZATION,
        HeaderValue::from_str(&format!("Bearer {}", MCP_TOKEN))?);
    headers.insert(HeaderName::from_static("x-client-version"),
        HeaderValue::from_static(CLIENT_VERSION));
    headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));

    if let Some(session_id) = get_session_id() {
        headers.insert(HeaderName::from_static("mcp-session-id"),
            HeaderValue::from_str(&session_id)?);
    }

    let client = reqwest::Client::new();
    let response = client.post(MCP_URL).headers(headers).body(body).send().await?;

    let response_session_id = response.headers()
        .get(HeaderName::from_static("mcp-session-id"))
        .and_then(|v| v.to_str().ok())
        .map(|s| s.to_string());
    set_session_id(response_session_id);

    Ok(response.text().await?.trim().to_string())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    mcp_call("initialize", Some(r#"{"protocolVersion": "2025-11-25"}"#)).await?;
    mcp_call("tools/call",
        Some(r#"{"name":"enterprise_query_state","arguments":{"nsrsbh":"91500112MAXXXX","username":"1325580xxxx"}}"#)).await?;
    Ok(())
}

平台接入教程

Cherry Studio

  1. 打开 Cherry Studio,进入设置页面
  2. 选择 「MCP」选项卡
  3. 点击 「添加」按钮
  4. 选择 「从 JSON 导入」,粘贴 MCP Server 配置 JSON
  5. 替换 YOUR_MCP_TOKEN 为实际 Token,点击确定
  6. 打开 启用开关,即可在聊天窗口中使用

参考:Cherry Studio MCP 文档

Cursor

  1. 打开 Cursor,点击 【设置】→【Tools & MCP】
  2. 点击 【Add Custom MCP】,在 mcp.json 中填入配置
  3. 替换 YOUR_MCP_TOKEN 为实际 Token,保存
  4. 确认服务状态显示 【已连接】
  5. CTRL/CMD + L 打开 Agent 对话框,开始使用

参考:Cursor MCP 文档

TRAE

  1. 打开 Trae,点击 【设置】→【MCP】→【手动添加】
  2. 填入 MCP Server 配置 JSON
  3. 替换 YOUR_MCP_TOKEN 为实际 Token,点击确认
  4. 确认服务状态显示 【已连接】
  5. 选择 【Builder with MCP】 模式,开始使用

参考:TRAE MCP 文档


典型应用场景

场景 说明
💬 对话式开票 对大模型说"帮我开一张发票",AI 自动完成全流程
📊 批量智能处理 上传 Excel 订单列表,AI 逐条解析并批量开票
🔍 智能发票查验 AI 自动识别发票信息并调用查验接口
🔄 自动化财税流程 结合 RPA 和 AI,实现订单→开票→入账全流程自动化
🏗️ 企业 AI 中台集成 将发票 API 作为企业 AI 中台的标准工具
📱 AI 客服自动开票 智能客服机器人直接为用户开具发票

常见问题

什么是 MCP (Model Context Protocol)?

MCP 是由 Anthropic 推出的开放标准协议,用于规范 AI 大模型与外部工具/API 之间的通信。fa-piao.com 的 MCP Server 实现了 Streamable HTTP 传输方式,支持实时流式响应。

支持哪些大模型平台?

支持所有兼容 MCP 协议的大模型平台,包括 Claude Desktop、Cursor、Cherry Studio、TRAE、Windsurf 等。只要平台支持 MCP 协议,即可直接使用。

MCP Server 的安全性如何保障?
  • API Key 认证,每个请求需携带有效 Token
  • 权限隔离,不同 API Key 可配置不同操作权限
  • TLS 1.3 加密传输
  • 请求频率限制,防止滥用
  • 完整的操作审计日志
使用 MCP Server 和直接调用 API 有什么区别?

MCP Server 让您通过自然语言与大模型交互来完成开票,无需编写代码。两者底层调用的是同一套发票 API,功能和性能完全一致。MCP Server 更适合非技术用户和追求效率的场景。

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

是的,MCP Server 功能包含在所有定价方案中,包括 1 分钱试用版。


相关链接

资源 链接
🌐 官网 fa-piao.com
📖 接口文档 fa-piao.com/doc.html
💰 价格方案 fa-piao.com/pricing.html
📦 SDK 资源 fa-piao.com/sdk.html
🔑 申请 Token open.fa-piao.com
💬 企业微信客服 work.weixin.qq.com

© 2025 fa-piao.com | 数电发票 API · 电子发票接口 · AI 开票 · MCP Server

About

电子发票MCP 数电发票MCP

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors