erpnext-mcp 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/PKG-INFO +1 -1
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/pyproject.toml +1 -1
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/src/erpnext_mcp/server.py +158 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/.env.example +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/.github/workflows/publish.yml +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/.gitignore +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/CLAUDE.md +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/LICENSE +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/README.md +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/docs/api-reference.md +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/docs/development-notes.md +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/docs/testing.md +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/mcp.json.example +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/src/erpnext_mcp/__init__.py +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/src/erpnext_mcp/client.py +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/src/erpnext_mcp/types.py +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/tests/__init__.py +0 -0
- {erpnext_mcp-0.2.0 → erpnext_mcp-0.3.0}/tests/test_integration.py +0 -0
|
@@ -430,6 +430,164 @@ async def download_file(file_name: str) -> dict:
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
|
|
433
|
+
# ── Supplier/Customer Details ──────────────────────────
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@mcp.tool()
|
|
437
|
+
async def get_supplier_details(name: str | None = None, keyword: str | None = None) -> dict:
|
|
438
|
+
"""Get complete supplier details including address, phone, and contacts.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
name: Exact supplier name (e.g. "SF0009-2 - 永心企業社")
|
|
442
|
+
keyword: Search keyword to find supplier (e.g. "永心")
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Dict with supplier info, address (phone/fax), and contacts (our purchaser + their contacts)
|
|
446
|
+
"""
|
|
447
|
+
client = get_client()
|
|
448
|
+
|
|
449
|
+
# Find supplier
|
|
450
|
+
if name:
|
|
451
|
+
supplier = await client.get_doc("Supplier", name)
|
|
452
|
+
elif keyword:
|
|
453
|
+
suppliers = await client.get_list(
|
|
454
|
+
"Supplier",
|
|
455
|
+
fields=["name", "supplier_name", "supplier_group", "country"],
|
|
456
|
+
filters={"name": ["like", f"%{keyword}%"]},
|
|
457
|
+
limit_page_length=1,
|
|
458
|
+
)
|
|
459
|
+
if not suppliers:
|
|
460
|
+
return {"error": f"找不到關鍵字「{keyword}」的供應商"}
|
|
461
|
+
supplier = await client.get_doc("Supplier", suppliers[0]["name"])
|
|
462
|
+
else:
|
|
463
|
+
return {"error": "請提供 name 或 keyword"}
|
|
464
|
+
|
|
465
|
+
supplier_name = supplier.get("name")
|
|
466
|
+
|
|
467
|
+
# Get address (phone/fax)
|
|
468
|
+
# Address title format: "代碼 地址", e.g. "SF0009-2 地址"
|
|
469
|
+
code = supplier_name.split(" - ")[0] if " - " in supplier_name else supplier_name
|
|
470
|
+
addresses = await client.get_list(
|
|
471
|
+
"Address",
|
|
472
|
+
fields=["address_title", "address_line1", "city", "pincode", "phone", "fax"],
|
|
473
|
+
filters={"address_title": ["like", f"%{code}%"]},
|
|
474
|
+
limit_page_length=5,
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# Get contacts via Dynamic Link
|
|
478
|
+
contacts = await client.get_list(
|
|
479
|
+
"Contact",
|
|
480
|
+
fields=["name", "first_name", "designation", "phone", "mobile_no", "email_id"],
|
|
481
|
+
filters=[["Dynamic Link", "link_name", "=", supplier_name]],
|
|
482
|
+
limit_page_length=50,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Categorize contacts
|
|
486
|
+
# 有 designation 的是我們的人(採購人員/業務人員),沒有的是對方的聯絡人
|
|
487
|
+
our_contacts = []
|
|
488
|
+
their_contacts = []
|
|
489
|
+
for c in contacts:
|
|
490
|
+
contact_info = {
|
|
491
|
+
"name": c.get("first_name") or c.get("name"),
|
|
492
|
+
"designation": c.get("designation") or "",
|
|
493
|
+
"phone": c.get("phone") or c.get("mobile_no") or "",
|
|
494
|
+
"email": c.get("email_id") or "",
|
|
495
|
+
}
|
|
496
|
+
if c.get("designation"):
|
|
497
|
+
our_contacts.append(contact_info)
|
|
498
|
+
else:
|
|
499
|
+
their_contacts.append(contact_info)
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
"supplier": {
|
|
503
|
+
"name": supplier_name,
|
|
504
|
+
"group": supplier.get("supplier_group"),
|
|
505
|
+
"country": supplier.get("country"),
|
|
506
|
+
"currency": supplier.get("default_currency"),
|
|
507
|
+
},
|
|
508
|
+
"address": addresses[0] if addresses else None,
|
|
509
|
+
"our_contacts": our_contacts,
|
|
510
|
+
"their_contacts": their_contacts,
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
@mcp.tool()
|
|
515
|
+
async def get_customer_details(name: str | None = None, keyword: str | None = None) -> dict:
|
|
516
|
+
"""Get complete customer details including address, phone, and contacts.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
name: Exact customer name (e.g. "CM0001 - 正達工程股份有限公司")
|
|
520
|
+
keyword: Search keyword to find customer (e.g. "正達")
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Dict with customer info, address (phone/fax), and contacts (our sales + their contacts)
|
|
524
|
+
"""
|
|
525
|
+
client = get_client()
|
|
526
|
+
|
|
527
|
+
# Find customer
|
|
528
|
+
if name:
|
|
529
|
+
customer = await client.get_doc("Customer", name)
|
|
530
|
+
elif keyword:
|
|
531
|
+
customers = await client.get_list(
|
|
532
|
+
"Customer",
|
|
533
|
+
fields=["name", "customer_name", "customer_group", "territory"],
|
|
534
|
+
filters={"name": ["like", f"%{keyword}%"]},
|
|
535
|
+
limit_page_length=1,
|
|
536
|
+
)
|
|
537
|
+
if not customers:
|
|
538
|
+
return {"error": f"找不到關鍵字「{keyword}」的客戶"}
|
|
539
|
+
customer = await client.get_doc("Customer", customers[0]["name"])
|
|
540
|
+
else:
|
|
541
|
+
return {"error": "請提供 name 或 keyword"}
|
|
542
|
+
|
|
543
|
+
customer_name = customer.get("name")
|
|
544
|
+
|
|
545
|
+
# Get address (phone/fax)
|
|
546
|
+
code = customer_name.split(" - ")[0] if " - " in customer_name else customer_name
|
|
547
|
+
addresses = await client.get_list(
|
|
548
|
+
"Address",
|
|
549
|
+
fields=["address_title", "address_line1", "city", "pincode", "phone", "fax"],
|
|
550
|
+
filters={"address_title": ["like", f"%{code}%"]},
|
|
551
|
+
limit_page_length=5,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
# Get contacts via Dynamic Link
|
|
555
|
+
contacts = await client.get_list(
|
|
556
|
+
"Contact",
|
|
557
|
+
fields=["name", "first_name", "designation", "phone", "mobile_no", "email_id"],
|
|
558
|
+
filters=[["Dynamic Link", "link_name", "=", customer_name]],
|
|
559
|
+
limit_page_length=50,
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
# Categorize contacts
|
|
563
|
+
# 有 designation 的是我們的人(採購人員/業務人員),沒有的是對方的聯絡人
|
|
564
|
+
our_contacts = []
|
|
565
|
+
their_contacts = []
|
|
566
|
+
for c in contacts:
|
|
567
|
+
contact_info = {
|
|
568
|
+
"name": c.get("first_name") or c.get("name"),
|
|
569
|
+
"designation": c.get("designation") or "",
|
|
570
|
+
"phone": c.get("phone") or c.get("mobile_no") or "",
|
|
571
|
+
"email": c.get("email_id") or "",
|
|
572
|
+
}
|
|
573
|
+
if c.get("designation"):
|
|
574
|
+
our_contacts.append(contact_info)
|
|
575
|
+
else:
|
|
576
|
+
their_contacts.append(contact_info)
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
"customer": {
|
|
580
|
+
"name": customer_name,
|
|
581
|
+
"group": customer.get("customer_group"),
|
|
582
|
+
"territory": customer.get("territory"),
|
|
583
|
+
"currency": customer.get("default_currency"),
|
|
584
|
+
},
|
|
585
|
+
"address": addresses[0] if addresses else None,
|
|
586
|
+
"our_contacts": our_contacts,
|
|
587
|
+
"their_contacts": their_contacts,
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
433
591
|
def main():
|
|
434
592
|
mcp.run()
|
|
435
593
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|